web-dev-qa-db-fra.com

Comment écrire des tests unitaires dans Spark 2.0+?

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 .

37
bbarker

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!

17
Shankar Koirala

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)

    }

  }

}
9
Powers

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.

5
Eugene Lopatkin

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.
        }
        }
1
sunitha

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)
0
Thirupathi Chavati