[PYTHON] So schreiben Sie einen Test für die Verarbeitung mit BigQuery

Dieser Artikel ist der Artikel zum 6. Tag des Adventskalenders 2014 des VOYAGE GROUP Engineer Blog.

Hallo, ist in der VOYAGE GROUP @ hagino3000 im Bereich Freizeitdatenwissenschaftler tätig.

Ich denke, es gibt viele Leute, die begonnen haben, Daten für die Analyse in BigQuery zu speichern, indem sie heutzutage die BigQuery-Bewegung fahren. Wenn BigQuery verwendet wird, wird Testcode wie Aggregatstapel in der lokalen Umgebung jedoch nicht abgeschlossen, und Sie sollten auf BigQuery selbst verweisen. Dieser Artikel stellt verschiedene Ansätze vor.

Der Beispielcode verwendet Python + Nase + BigQuery-Python.

Was ist los

Der Grund für die Besorgnis über Testcode ist, dass BigQuery die folgenden zwei Funktionen bietet.

  1. Ich kann keine lokale Umgebung erstellen
  2. Es dauert ungefähr 5 Sekunden, um auch nur eine kleine Datenmenge abzufragen

Zumal die Abfrage lange dauert, möchte ich dies im Test verkürzen.

Mach alles verspottet

BigQuery-Python-Testcode greift beispielsweise überhaupt nicht auf BigQuery zu.

https://github.com/tylertreat/BigQuery-Python/blob/master/bigquery/tests/test_client.py

Auf jeden Fall hat es den Vorteil, mit hoher Geschwindigkeit zu arbeiten, aber es ist nicht möglich zu bestätigen, ob der INSERT-Prozess und die SELECT-Anweisung tatsächlich funktionieren. Außerdem ist der Testcode voll von Mock.

Erstellen Sie für jedes Testmodul einen Datensatz

Einfach ausgedrückt wäre es schön, einen Datensatz für Unit-Tests zu haben. Da dies jedoch stört, wenn mehrere Personen gleichzeitig den Test ausführen, ist für jeden Testlauf ein Datensatz erforderlich. Machen Sie jedes Mal, wenn Sie einen Test ausführen, dasselbe wie Django mit "Datenbank erstellen".

Zunächst der Prozess zum Erstellen eines Einwegdatensatzes (+ Tabelle).

tests/helper.py


# coding=utf-8
from datetime import datetime
import glob
import json
import os
import random
import re


def setup_dataset(client, test_name):
    """
Bereiten Sie einen Datensatz zum Testen vor

    Parameters
    ----------
    client : bigquery.client
        See https://github.com/tylertreat/BigQuery-Python

    Returns
    -------
    dataset_id : string
ID des erstellten Datensatzes(ex. ut_hoge_test_359103)

    schemas : dict (key: string, value: list)
Der Schlüssel ist der Tabellenname und der Wert ist die Schemadefinitionsliste, die zum Erstellen der Tabelle verwendet wird.
    """
    #Erstellen eines Datensatzes
    dataset_id = 'ut_%s_%d' % (test_name ,int(random.random() * 1000000))
    client.create_dataset(
        dataset_id,
        friendly_name='For unit test started at %s' % datetime.now())

    #Erstellen Sie eine Tabelle aus einer Schemadefinitionsdatei
    schemas = {}
    BASE_DIR = os.path.normpath(os.path.join(os.path.dirname(__file__), '../'))
    for schema_file in glob.glob(os.path.join(BASE_DIR, 'schema/*.json')):
        table_name = re.search(r'([^/.]+).json$', schema_file).group(1)
        schema = json.loads(open(schema_file).read())
        schemas[table_name] = schema
        client.create_table(dataset_id, table_name, schema)

    return dataset_id, schemas

Erstellen Sie einen Datensatz mit dem Testkörper und dem Setup. Um diesen Datensatz für die zu testende Verarbeitung zu verwenden, wird der Prozess des Erfassens der Datensatz-ID in Mock konvertiert.

test_hoge.py


# coding=utf-8
import time

import mock
from nose.tools import eq_
from nose.plugins.attrib import attr

import myapp.bq
import myapp.calc_daily_state
from . import helper

dataset_id = None
bq_client = None

#Parallel laufen
_multiprocess_can_split_ = True


def setup():
    global dataset_id
    global bq_client
    # BigQuery-Holen Sie sich eine Python-Client-Instanz
    bq_client = myapp.bq.get_client(readonly=False)
    #Erstellen eines Datensatzes
    dataset_id, schemas = helper.setup_dataset(bq_client, 'test_hoge')
    #Verspotten Sie den Prozess, um die Dataset-ID zu erhalten
    myapp.bq.get_dataset_id = mock.Mock(return_value=dataset_id)
    #INSERT von Testdaten
    bq_client.push_rows(dataset_id, 'events', [....Abkürzung....])
    #Es ist möglicherweise nicht möglich, unmittelbar nach dem Einfügen eine Abfrage durchzuführen
    time.sleep(10)


@attr('slow')
def test_calc_dau():
    #Tests, die auf BigQuery verweisen
    ret = myapp.calc_daily_state.calc_dau('2014/08/01')
    eq_(ret, "....Abkürzung....")


@attr('slow')
def test_calc_new_user():
    #Tests, die auf BigQuery verweisen
    ret = myapp.calc_daily_state.calc_new_user('2014/08/01')
    eq_(ret, "....Abkürzung....")


def teadown():
    #Es scheint besser, den Datensatz zu löschen und zu belassen, wenn ein Test fehlgeschlagen ist
    bq_client.delete_dataset(dataset_id)

In diesem Beispiel wurde angenommen, dass der zu testende Prozess ReadOnly ist, sodass der Datensatz nur einmal erstellt wurde. Es dauert 5 Sekunden für jeden Testfall, daher möchte ich 1 Test und 1 Assert einhalten.

Es dauert ungefähr 1 Sekunde, um ein Setup-Dataset zu erstellen und die Daten zu laden. Da jeder Fall Zeit braucht, ist es möglich, die Zeit durch Parallelisieren etwas zu verkürzen.

#5 Führen Sie die Tests parallel aus
nosetests --processes=5 --process-timeout=30

Multiprocess: parallel testing — nose 1.3.4 documentation http://nose.readthedocs.org/en/latest/plugins/multiprocess.html

Erstellen Sie für jede Testfunktion einen Datensatz

Im obigen Beispiel wurde der Datensatz durch Einrichten des Moduls erstellt. Wenn Sie jedoch einen Prozess mit INSERT testen möchten, müssen Sie den Einfluss zwischen den Tests beseitigen. Dies wird noch länger dauern. Wenn Sie versuchen, das Ergebnis unmittelbar nach INSERT zu überprüfen und die Abfrage auszuführen, erhalten Sie das Ergebnis nicht. Nach dem EINFÜGEN müssen Sie einige Sekunden schlafen und dann die Abfrage ausführen (die etwa 5 Sekunden dauert), um das Ergebnis zu überprüfen.

test_fuga.py


#Parallel laufen
_multiprocess_can_split_ = True

@attr('slow')
class TestFugaMethodsWhichHasInsert(object):
    def setup(self):
        #Erstellen Sie einen Datensatz
        (Abkürzung)
        self.dataset_id = dataset_id
        self.bq_client = bq_client
    
    def test_insert_foo(self):
        #Testen der Verarbeitung mit INSERT

    def test_insert_bar(self):
        #Testen der Verarbeitung mit INSERT

    def teardown(self):
        self.bq_client.delete_dataset(self.dataset_id)

Es ist unvermeidlich, am Ende des Tests einzuschlafen. Überlassen wir es also dem CI-Tool. In diesem Fall können BigQuery-Daten parallel ausgeführt werden, da der Einfluss zwischen Tests entfernt werden kann.

Ein ausgewogener Kompromiss

Nur die Methode zum Ausgeben einer Abfrage und zum Einfügen von Daten verwendet die tatsächliche BigQuery und trennt die Verzeichnisse als langsamen Test. Bei einer anderen Verarbeitung wird der Teil, der das Ergebnis der Abfrage zurückgibt, in Mock konvertiert.

Radikale

Schreiben Sie keine Tests in den Code der Analyseaufgabe

Zukünftige Schule

Warten Sie, bis etwas wie DynamoDB Local angezeigt wird. Oder mach es.

Zusammenfassung

Derzeit gibt es keine Option, dass dies die beste ist. Wenn Sie also Mock reduzieren und Ihren Testcode einfach halten möchten, wenden Sie sich direkt an BigQuery und umgekehrt an Mock. Entfernen Sie die Abhängigkeiten zwischen den Tests, damit die Tests parallel ausgeführt werden können. Das Erstellen eines Datensatzes dauert nicht lange, daher ist es in Ordnung, ihn für jeden Test zu erstellen.

Ich würde es begrüßen, wenn Sie mir sagen könnten, ob es ein besseres Muster gibt.

Die Gebühr von morgen ist @brtriver. Bitte freuen Sie sich darauf.

Hinweis: Wie lautet der Testcode für Google-BigQuery-Tools?

Mal sehen, was mit dem Testcode des von Python erstellten Befehls bq passiert. Sie können eine Abfrage mit `` `bq query xxx``` ausgeben, damit Sie einen solchen Test durchführen können.

https://code.google.com/p/google-bigquery-tools/source/browse/bq/bigquery_client_test.py

Es gibt keinen Test zum Ausführen der Abfrage. (´ ・ ω ・ `)

Recommended Posts

So schreiben Sie einen Test für die Verarbeitung mit BigQuery
So schreiben Sie einen ShellScript Bash für Anweisung
[Python] So schreiben Sie eine Dokumentzeichenfolge, die PEP8 entspricht
Vergleichen Sie, wie die Verarbeitung für Listen nach Sprache geschrieben wird
So schreiben Sie eine Meta-Klasse, die sowohl Python2 als auch Python3 unterstützt
So führen Sie einen Komponententest durch Teil 1 Entwurfsmuster zur Einführung
So testen Sie auf einer von Django authentifizierten Seite
Ein Memorandum darüber, wie man Pandas schreibt, das ich persönlich oft vergesse
Wie schreibe ich ein benanntes Tupeldokument im Jahr 2020?
[Go] So schreiben oder rufen Sie eine Funktion auf
So schreiben Sie eine ShellScript-Bash-Case-Anweisung
[BigQuery] Verwendung der BigQuery-API für die Python-Tabellenerstellung-
Ich möchte in Python schreiben! (2) Schreiben wir einen Test
[Go] So erstellen Sie einen benutzerdefinierten Fehler für Sentry
So schreiben Sie einen Listen- / Wörterbuchtyp von Python3
So erstellen Sie ein lokales Repository für Linux
So erstellen Sie eine Entwicklungsumgebung für TensorFlow (1.0.0) (Mac)
Schreiben Sie Code in UnitTest, eine Python-Webanwendung
[Einführung in Python] So schreiben Sie sich wiederholende Anweisungen mit for-Anweisungen
Verwendung von pip, einem Paketverwaltungssystem, das für die Verwendung von Python unverzichtbar ist
So testen Sie, ob die Ausnahme in Python unittest ausgelöst wird
So verwalten Sie eine README-Datei für Github und PyPI
So rufen Sie eine Funktion auf
Wie erstelle ich ein Python-Paket (geschrieben für Praktikanten)
So ersetzen Sie eine Teilübereinstimmung durch einen numerischen Wert (Anmerkung 1)
Wie man ein Terminal hackt
Ich werde nie vergessen, wie man ein Shell-Skript schreibt, nicht vergessen! !!
So schreiben Sie Typhinweise für Variablen, die mehrfach in einer Zeile zugewiesen werden
[Einführung in Python] Wie verwende ich den Operator in in der for-Anweisung?
Spigot (Papier) Einführung in die Erstellung eines Plug-Ins für 2020 # 01 (Umgebungskonstruktion)
Eine Geschichte, die Schwierigkeiten hatte, 3 Millionen ID-Daten in einer Schleife zu verarbeiten
Schreiben Sie ein Programm, das das Programm missbraucht und 100 E-Mails sendet
Wie erstelle ich eine große Menge an Testdaten in MySQL? ??
Ein Skript, das Boto verwendet, um einen bestimmten Ordner in Amason S3 hochzuladen
So schreiben Sie eine Dokumentzeichenfolge, um ein benanntes Tupeldokument mit Sphinx zu erstellen
So setzen Sie die Verarbeitung fort, nachdem Sie eine Antwort auf aiohttp Server zurückgegeben haben
Wie erstelle ich eine japanisch-englische Übersetzung?
Schreiben Sie einen tabellengesteuerten Test in C.
[Django] So testen Sie Form [TDD]
So setzen Sie einen symbolischen Link
[Für Nicht-Programmierer] Wie man Kaggle läuft
Wie man nüchtern mit Pandas schreibt
So erstellen Sie ein Conda-Paket
Schreiben Sie die Standardausgabe in eine Datei
Wie erstelle ich einen Crawler?
So erstellen Sie eine rekursive Funktion
So erstellen Sie eine virtuelle Brücke
Wie erstelle ich eine Docker-Datei?
[Blender] So erstellen Sie ein Blender-Plug-In
So löschen Sie einen Docker-Container
So schreiben Sie ein Docker-Basis-Image
Wie schreibe ich Django1.9 umweltunabhängig wsgi.py
Wie erstelle ich einen Crawler?
Hinweise zum Schreiben von require.txt