J'ai essayé de trouver un moyen raisonnable de tester SparkSession
avec le framework de test JUnit. Bien qu'il semble y avoir de bons exemples pour SparkContext
, je ne pouvais pas comprendre comment obtenir un exemple correspondant pour SparkSession
, même s'il est utilisé à plusieurs endroits en interne dans spark-testing-base . Je serais heureux d'essayer une solution qui n'utilise pas non plus de base de test d'étincelles si ce n'est pas vraiment la bonne façon de s'y prendre.
Cas de test simple ( projet MWE complet avec build.sbt
):
import com.holdenkarau.spark.testing.DataFrameSuiteBase
import org.junit.Test
import org.scalatest.FunSuite
import org.Apache.spark.sql.SparkSession
class SessionTest extends FunSuite with DataFrameSuiteBase {
implicit val sparkImpl: SparkSession = spark
@Test
def simpleLookupTest {
val homeDir = System.getProperty("user.home")
val training = spark.read.format("libsvm")
.load(s"$homeDir\\Documents\\GitHub\\sample_linear_regression_data.txt")
println("completed simple lookup test")
}
}
Le résultat de cette opération avec JUnit est un NPE sur la ligne de chargement:
Java.lang.NullPointerException
at SessionTest.simpleLookupTest(SessionTest.scala:16)
at Sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at Sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.Java:62)
at Sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.Java:43)
at Java.lang.reflect.Method.invoke(Method.Java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.Java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.Java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.Java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.Java:17)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.Java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.Java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.Java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.Java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.Java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.Java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.Java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.Java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.Java:363)
at org.junit.runner.JUnitCore.run(JUnitCore.Java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.Java:68)
at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.Java:51)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.Java:237)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.Java:70)
Notez que le fichier en cours de chargement n’a aucune importance; Dans une SparkSession correctement configurée, une erreur plus sensible sera renvoyée .
Vous pouvez écrire un test simple avec FunSuite et BeforeAndAfterEach comme ci-dessous
class Tests extends FunSuite with BeforeAndAfterEach {
var sparkSession : SparkSession = _
override def beforeEach() {
sparkSession = SparkSession.builder().appName("udf testings")
.master("local")
.config("", "")
.getOrCreate()
}
test("your test name here"){
//your unit test assert here like below
assert("True".toLowerCase == "true")
}
override def afterEach() {
sparkSession.stop()
}
}
Vous n'avez pas besoin de créer une fonction dans le test, vous pouvez simplement écrire en tant que
test ("test name") {//implementation and assert}
Holden Karau a écrit vraiment Nice test spark-testing-base
Vous devez vérifier ci-dessous est un exemple simple
class TestSharedSparkContext extends FunSuite with SharedSparkContext {
val expectedResult = List(("a", 3),("b", 2),("c", 4))
test("Word counts should be equal to expected") {
verifyWordCount(Seq("c a a b a c b c c"))
}
def verifyWordCount(seq: Seq[String]): Unit = {
assertResult(expectedResult)(new WordCount().transform(sc.makeRDD(seq)).collect().toList)
}
}
J'espère que cela t'aides!
J'aime créer un trait SparkSessionTestWrapper
qui peut être mélangé pour tester des classes. L'approche de Shankar fonctionne, mais elle est extrêmement lente pour les suites de tests contenant plusieurs fichiers.
import org.Apache.spark.sql.SparkSession
trait SparkSessionTestWrapper {
lazy val spark: SparkSession = {
SparkSession.builder().master("local").appName("spark session").getOrCreate()
}
}
Le trait peut être utilisé comme suit:
class DatasetSpec extends FunSpec with SparkSessionTestWrapper {
import spark.implicits._
describe("#count") {
it("returns a count of all the rows in a DataFrame") {
val sourceDF = Seq(
("jets"),
("barcelona")
).toDF("team")
assert(sourceDF.count === 2)
}
}
}
Consultez le projet spark-spec } _ pour obtenir un exemple concret utilisant l'approche SparkSessionTestWrapper
.
Mettre à jour
La bibliothèque d'étalonnage-test-de-base ajoute automatiquement la SparkSession lorsque certains traits sont mélangés à la classe de test (par exemple, lorsque DataFrameSuiteBase
est mélangé, vous aurez accès à SparkSession via la variable spark
.
J'ai créé une bibliothèque de tests séparée appelée spark-fast-tests afin de donner aux utilisateurs le contrôle total de SparkSession lorsqu'ils exécutent leurs tests. Je ne pense pas qu'une bibliothèque d'assistance de test devrait définir la SparkSession. Les utilisateurs doivent pouvoir démarrer et arrêter leur SparkSession comme bon leur semble (j'aime bien créer une SparkSession et l'utiliser tout au long de l'exécution de la suite de tests).
Voici un exemple de la méthode assertSmallDatasetEquality
de spark-fast-tests-test:
import com.github.mrpowers.spark.fast.tests.DatasetComparer
class DatasetSpec extends FunSpec with SparkSessionTestWrapper with DatasetComparer {
import spark.implicits._
it("aliases a DataFrame") {
val sourceDF = Seq(
("jose"),
("li"),
("luisa")
).toDF("name")
val actualDF = sourceDF.select(col("name").alias("student"))
val expectedDF = Seq(
("jose"),
("li"),
("luisa")
).toDF("student")
assertSmallDatasetEquality(actualDF, expectedDF)
}
}
}
Depuis Spark 1.6, vous pouvez utiliser SharedSparkContext
ou SharedSQLContext
que Spark utilise pour ses propres tests unitaires:
class YourAppTest extends SharedSQLContext {
var app: YourApp = _
protected override def beforeAll(): Unit = {
super.beforeAll()
app = new YourApp
}
protected override def afterAll(): Unit = {
super.afterAll()
}
test("Your test") {
val df = sqlContext.read.json("examples/src/main/resources/people.json")
app.run(df)
}
Depuis Spark 2.3SharedSparkSession
est disponible:
class YourAppTest extends SharedSparkSession {
var app: YourApp = _
protected override def beforeAll(): Unit = {
super.beforeAll()
app = new YourApp
}
protected override def afterAll(): Unit = {
super.afterAll()
}
test("Your test") {
df = spark.read.json("examples/src/main/resources/people.json")
app.run(df)
}
Mettre à jour:
Dépendance Maven:
<dependency>
<groupId>org.Apache.spark</groupId>
<artifactId>spark-sql</artifactId>
<version>SPARK_VERSION</version>
<type>test-jar</type>
<scope>test</scope>
</dependency>
Dépendance SBT:
"org.Apache.spark" %% "spark-sql" % SPARK_VERSION % Test classifier "tests"
En outre, vous pouvez vérifier sources de test de Spark où il existe un vaste ensemble de combinaisons de test différentes.
Je pourrais résoudre le problème avec le code ci-dessous
la dépendance spark-Hive est ajoutée dans le projet pom
class DataFrameTest extends FunSuite with DataFrameSuiteBase{
test("test dataframe"){
val sparkSession=spark
import sparkSession.implicits._
var df=sparkSession.read.format("csv").load("path/to/csv")
//rest of the operations.
}
}
Une autre façon de tester l'unité en utilisant JUnit
import org.Apache.spark.sql.SparkSession
import org.junit.Assert._
import org.junit.{After, Before, _}
@Test
class SessionSparkTest {
var spark: SparkSession = _
@Before
def beforeFunction(): Unit = {
//spark = SessionSpark.getSparkSession()
spark = SparkSession.builder().appName("App Name").master("local").getOrCreate()
System.out.println("Before Function")
}
@After
def afterFunction(): Unit = {
spark.stop()
System.out.println("After Function")
}
@Test
def testRddCount() = {
val rdd = spark.sparkContext.parallelize(List(1, 2, 3))
val count = rdd.count()
assertTrue(3 == count)
}
@Test
def testDfNotEmpty() = {
val sqlContext = spark.sqlContext
import sqlContext.implicits._
val numDf = spark.sparkContext.parallelize(List(1, 2, 3)).toDF("nums")
assertFalse(numDf.head(1).isEmpty)
}
@Test
def testDfEmpty() = {
val sqlContext = spark.sqlContext
import sqlContext.implicits._
val emptyDf = spark.sqlContext.createDataset(spark.sparkContext.emptyRDD[Num])
assertTrue(emptyDf.head(1).isEmpty)
}
}
case class Num(id: Int)