web-dev-qa-db-fra.com

Comment puis-je tester les programmes PySpark à l'unité?

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?

30
0111001101110000

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
24
Vikas Kawadia

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.

15
ksindi

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).

8
Jorge Leitão

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.

8
santon

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:

  1. Installez Apache Spark (configurez JVM + décompressez la distribution de Spark dans un répertoire)
  2. Installer "pytest" + plugin "pytest-spark"
  3. Créez "pytest.ini" dans le répertoire de votre projet et spécifiez Spark emplacement là-bas.
  4. Exécutez vos tests par pytest comme d'habitude.
  5. En option, vous pouvez utiliser le luminaire "spark_context" dans vos tests qui est fourni par le plugin - il essaie de minimiser les journaux de Spark dans la sortie.
1
Alex Markov