Merci d'avoir posé cette question exceptionnelle. Pour une raison quelconque, lorsqu'il s'agit de Spark, tout le monde est tellement pris par l'analyse qu'il oublie les grandes pratiques de génie logiciel qui ont émergé au cours des 15 dernières années environ. C'est pourquoi nous mettons un point d'honneur à discuter des tests et de l'intégration continue (entre autres choses comme DevOps) dans notre cours.
Un bref rappel de la terminologie
A vrai Un test unitaire signifie que vous avez un contrôle total sur chaque composant du test. Il ne peut y avoir aucune interaction avec les bases de données, les appels REST, les systèmes de fichiers, ou même l'horloge du système ; tout doit être "doublé" (c'est-à-dire simulé, bloqué, etc.) comme le dit Gerard Mezaros dans Patrons de test xUnit . Je sais que cela semble être de la sémantique, mais c'est vraiment important. Ne pas comprendre cela est l'une des principales raisons pour lesquelles vous voyez des échecs de test intermittents dans l'intégration continue.
On peut toujours faire des tests unitaires
Donc, étant donné cette compréhension, les tests unitaires et RDD
est impossible. Cependant, les tests unitaires ont toujours leur place dans le développement de l'analytique.
Considérons une opération simple :
rdd.map(foo).map(bar)
Ici foo
et bar
sont des fonctions simples. Elles peuvent être testées à l'unité de la manière habituelle, et elles devraient l'être avec autant de cas de figure que possible. Après tout, pourquoi se préoccuper de l'endroit d'où proviennent les entrées, qu'il s'agisse d'un dispositif de test ou d'un système d'exploitation. RDD
?
N'oubliez pas l'étincelle
Ce n'est pas un test en soi Mais à ces premiers stades, vous devriez également faire des expériences dans le shell Spark pour déterminer vos transformations et surtout les conséquences de votre approche. Par exemple, vous pouvez examiner les plans de requête physiques et logiques, la stratégie de partitionnement et la préservation, ainsi que l'état de vos données à l'aide de nombreuses fonctions différentes telles que toDebugString
, explain
, glom
, show
, printSchema
et ainsi de suite. Je vous laisse les explorer.
Vous pouvez également régler votre maître sur local[2]
dans le shell Spark et dans vos tests afin d'identifier tout problème qui pourrait ne survenir que lorsque vous commencerez à distribuer le travail.
Tests d'intégration avec Spark
Maintenant, passons aux choses amusantes.
Afin de test d'intégration Spark après que vous ayez confiance dans la qualité de vos fonctions d'aide et RDD
/ DataFrame
En ce qui concerne la logique de transformation, il est essentiel de faire quelques choses (indépendamment de l'outil de construction et du cadre de test) :
- Augmenter la mémoire de la JVM.
- Active la bifurcation mais désactive l'exécution parallèle.
- Utilisez votre cadre de test pour accumuler vos tests d'intégration Spark dans des suites, et initialisez le fichier
SparkContext
avant tous les tests et l'arrêter après tous les tests.
Avec ScalaTest, vous pouvez mélanger dans BeforeAndAfterAll
(que je préfère généralement) ou BeforeAndAfterEach
comme le fait @ShankarKoirala pour initialiser et démonter les artefacts Spark. Je sais qu'il s'agit d'un endroit raisonnable pour faire une exception, mais je n'aime vraiment pas ces objets mutables. var
que vous devez utiliser.
Le modèle de prêt
Une autre approche consiste à utiliser le Types de prêts .
Par exemple (en utilisant ScalaTest) :
class MySpec extends WordSpec with Matchers with SparkContextSetup {
"My analytics" should {
"calculate the right thing" in withSparkContext { (sparkContext) =>
val data = Seq(...)
val rdd = sparkContext.parallelize(data)
val total = rdd.map(...).filter(...).map(...).reduce(_ + _)
total shouldBe 1000
}
}
}
trait SparkContextSetup {
def withSparkContext(testMethod: (SparkContext) => Any) {
val conf = new SparkConf()
.setMaster("local")
.setAppName("Spark test")
val sparkContext = new SparkContext(conf)
try {
testMethod(sparkContext)
}
finally sparkContext.stop()
}
}
Comme vous pouvez le constater, le modèle de prêt fait appel à des fonctions d'ordre supérieur pour "prêter" l'objet de l'opération. SparkContext
à l'épreuve, puis de s'en débarrasser une fois l'épreuve terminée.
La programmation orientée vers la souffrance (Merci, Nathan)
C'est une question de préférence, mais je préfère utiliser le modèle de prêt et câbler les choses moi-même aussi longtemps que possible avant de faire appel à un autre cadre. En plus d'essayer de rester léger, les frameworks ajoutent parfois beaucoup de "magie" qui rend le débogage des échecs de test difficile à raisonner. Je prends donc une Programmation axée sur la souffrance où j'évite d'ajouter un nouveau cadre jusqu'à ce que la douleur de ne pas l'avoir soit trop dure à supporter. Mais encore une fois, c'est à vous de voir.
Le meilleur choix pour ce cadre alternatif est bien sûr spark-testing-base comme l'a mentionné @ShankarKoirala. Dans ce cas, le test ci-dessus ressemblerait à ceci :
class MySpec extends WordSpec with Matchers with SharedSparkContext {
"My analytics" should {
"calculate the right thing" in {
val data = Seq(...)
val rdd = sc.parallelize(data)
val total = rdd.map(...).filter(...).map(...).reduce(_ + _)
total shouldBe 1000
}
}
}
Notez que je n'ai pas eu à faire quoi que ce soit pour gérer le SparkContext
. SharedSparkContext
m'a donné tout ça avec sc
comme le SparkContext
-- gratuitement. Personnellement, je n'utiliserais pas cette dépendance dans ce seul but, car le Loan Pattern fait exactement ce dont j'ai besoin pour cela. De plus, compte tenu de l'imprévisibilité des systèmes distribués, il peut être très pénible de devoir retracer la magie qui se produit dans le code source d'une bibliothèque tierce lorsque les choses tournent mal lors de l'intégration continue.
Maintenant où spark-testing-base Les aides basées sur Hadoop telles que HDFSClusterLike
et YARNClusterLike
. Le fait de combiner ces caractéristiques peut vraiment vous épargner bien des soucis de configuration. Un autre endroit où il brille est avec le Scalacheck -comme les propriétés et les générateurs - en supposant bien sûr que vous compreniez comment les tests basés sur les propriétés fonctionnent et pourquoi ils sont utiles. Mais encore une fois, j'hésiterais à l'utiliser jusqu'à ce que mes analyses et mes tests atteignent ce niveau de sophistication.
"Seul un Sith traite avec des absolus." -- Obi-Wan Kenobi
Bien sûr, vous n'êtes pas obligé de choisir l'un ou l'autre. Vous pourriez peut-être utiliser l'approche Loan Pattern pour la plupart de vos tests et spark-testing-base seulement pour quelques tests plus rigoureux. Le choix n'est pas binaire ; vous pouvez faire les deux.
Tests d'intégration avec Spark Streaming
Enfin, je voudrais simplement présenter un extrait de ce à quoi pourrait ressembler une configuration de test d'intégration de SparkStreaming avec des valeurs en mémoire sans spark-testing-base :
val sparkContext: SparkContext = ...
val data: Seq[(String, String)] = Seq(("a", "1"), ("b", "2"), ("c", "3"))
val rdd: RDD[(String, String)] = sparkContext.parallelize(data)
val strings: mutable.Queue[RDD[(String, String)]] = mutable.Queue.empty[RDD[(String, String)]]
val streamingContext = new StreamingContext(sparkContext, Seconds(1))
val dStream: InputDStream = streamingContext.queueStream(strings)
strings += rdd
C'est plus simple qu'il n'y paraît. Il s'agit simplement de transformer une séquence de données en une file d'attente à destination de l'application DStream
. La plupart d'entre eux ne sont en fait qu'une configuration passe-partout qui fonctionne avec les API de Spark. Quoi qu'il en soit, vous pouvez le comparer avec StreamingSuiteBase
comme on le trouve dans spark-testing-base pour décider de ce que vous préférez.
C'est peut-être mon post le plus long, alors je vais le laisser ici. J'espère que d'autres personnes apporteront d'autres idées pour améliorer la qualité de nos analyses grâce aux mêmes pratiques de génie logiciel agile qui ont amélioré le développement de toutes les autres applications.
Et avec mes excuses pour cette publicité éhontée, vous pouvez consulter notre cours. Analytique avec Apache Spark où nous abordons beaucoup de ces idées et plus encore. Nous espérons avoir bientôt une version en ligne.
1 votes
Merci à tous pour les réponses reçues jusqu'à présent ; j'espère pouvoir faire le point bientôt. J'ai également ouvert une question et je la renvoie ici : github.com/holdenk/spark-testing-base/issues/180
0 votes
Malheureusement, je n'ai pas encore eu l'occasion d'utiliser Spark ... un jour, peut-être 3.x à ce rythme - sinon je travaillerais à accepter une réponse. Je suis heureux que cela ait été utile à d'autres.