Mon approche actuelle de test unitaire Java/Spark fonctionne (détaillée ici ) en instanciant un SparkContext en utilisant "local" et en exécutant des tests unitaires en utilisant JUnit.
Le code doit être organisé pour effectuer des E/S dans une fonction, puis appeler une autre avec plusieurs RDD.
Cela fonctionne très bien. J'ai une transformation de données hautement testée écrite en Java + Spark.
Puis-je faire de même avec Python?
Comment exécuter des tests unitaires Spark avec Python?
Je recommanderais également d'utiliser py.test. py.test permet de créer facilement des montages de test SparkContext réutilisables et de l'utiliser pour écrire des fonctions de test concises. Vous pouvez également spécialiser les appareils (pour créer un StreamingContext par exemple) et en utiliser un ou plusieurs dans vos tests.
J'ai écrit un article de blog sur Medium sur ce sujet:
https://engblog.nextdoor.com/unit-testing-Apache-spark-with-py-test-3b8970dc013b
Voici un extrait du message:
pytestmark = pytest.mark.usefixtures("spark_context")
def test_do_Word_counts(spark_context):
""" test Word couting
Args:
spark_context: test fixture SparkContext
"""
test_input = [
' hello spark ',
' hello again spark spark'
]
input_rdd = spark_context.parallelize(test_input, 1)
results = wordcount.do_Word_counts(input_rdd)
expected_results = {'hello':2, 'spark':3, 'again':1}
assert results == expected_results
Voici une solution avec pytest si vous utilisez Spark 2.x et SparkSession
. J'importe également un package tiers.
import logging
import pytest
from pyspark.sql import SparkSession
def quiet_py4j():
"""Suppress spark logging for the test context."""
logger = logging.getLogger('py4j')
logger.setLevel(logging.WARN)
@pytest.fixture(scope="session")
def spark_session(request):
"""Fixture for creating a spark context."""
spark = (SparkSession
.builder
.master('local[2]')
.config('spark.jars.packages', 'com.databricks:spark-avro_2.11:3.0.1')
.appName('pytest-pyspark-local-testing')
.enableHiveSupport()
.getOrCreate())
request.addfinalizer(lambda: spark.stop())
quiet_py4j()
return spark
def test_my_app(spark_session):
...
Notez que si vous utilisez Python 3, je devais spécifier cela en tant que variable d'environnement PYSPARK_PYTHON:
import os
import sys
IS_PY2 = sys.version_info < (3,)
if not IS_PY2:
os.environ['PYSPARK_PYTHON'] = 'python3'
Sinon, vous obtenez l'erreur:
Exception: Python dans le travailleur a une version 2.7 différente de celle du pilote 3.5, PySpark ne peut pas fonctionner avec différentes versions mineures. Veuillez vérifier les variables d'environnement PYSPARK_PYTHON et PYSPARK_DRIVER_PYTHON sont correctement définies.
En supposant que vous avez pyspark
installé, vous pouvez utiliser la classe ci-dessous pour unitTest dans unittest
:
import unittest
import pyspark
class PySparkTestCase(unittest.TestCase):
@classmethod
def setUpClass(cls):
conf = pyspark.SparkConf().setMaster("local[2]").setAppName("testing")
cls.sc = pyspark.SparkContext(conf=conf)
cls.spark = pyspark.SQLContext(cls.sc)
@classmethod
def tearDownClass(cls):
cls.sc.stop()
Exemple:
class SimpleTestCase(PySparkTestCase):
def test_with_rdd(self):
test_input = [
' hello spark ',
' hello again spark spark'
]
input_rdd = self.sc.parallelize(test_input, 1)
from operator import add
results = input_rdd.flatMap(lambda x: x.split()).map(lambda x: (x, 1)).reduceByKey(add).collect()
self.assertEqual(results, [('hello', 2), ('spark', 3), ('again', 1)])
def test_with_df(self):
df = self.spark.createDataFrame(data=[[1, 'a'], [2, 'b']],
schema=['c1', 'c2'])
self.assertEqual(df.count(), 2)
Notez que cela crée un contexte par classe. Utilisez setUp
au lieu de setUpClass
pour obtenir un contexte par test. Cela ajoute généralement beaucoup de temps supplémentaire à l'exécution des tests, car la création d'un nouveau contexte spark est actuellement coûteuse).
J'utilise pytest
, qui permet des montages de test afin que vous puissiez instancier un contexte pyspark et l'injecter dans tous vos tests qui en ont besoin. Quelque chose dans le sens de
@pytest.fixture(scope="session",
params=[pytest.mark.spark_local('local'),
pytest.mark.spark_yarn('yarn')])
def spark_context(request):
if request.param == 'local':
conf = (SparkConf()
.setMaster("local[2]")
.setAppName("pytest-pyspark-local-testing")
)
Elif request.param == 'yarn':
conf = (SparkConf()
.setMaster("yarn-client")
.setAppName("pytest-pyspark-yarn-testing")
.set("spark.executor.memory", "1g")
.set("spark.executor.instances", 2)
)
request.addfinalizer(lambda: sc.stop())
sc = SparkContext(conf=conf)
return sc
def my_test_that_requires_sc(spark_context):
assert spark_context.textFile('/path/to/a/file').count() == 10
Vous pouvez ensuite exécuter les tests en mode local en appelant py.test -m spark_local
ou dans YARN avec py.test -m spark_yarn
. Cela a plutôt bien fonctionné pour moi.
Il y a quelque temps, j'ai également rencontré le même problème et après avoir lu plusieurs articles, forums et réponses StackOverflow, j'ai fini par écrire un petit plugin pour pytest: pytest-spark
Je l'utilise déjà depuis quelques mois et le workflow général semble bon sous Linux: