it-swarm.com.de

Unit-Tests für Funktionen in einem Jupyter-Notebook?

Ich habe ein Jupyter-Notizbuch, das ich wiederholt ausführen möchte. Es hat Funktionen, die Struktur des Codes ist wie folgt:

def construct_url(data):
    ...
    return url

def scrape_url(url):
    ... # fetch url, extract data
    return parsed_data

for i in mylist: 
    url = construct_url(i)
    data = scrape_url(url)
    ... # use the data to do analysis

Ich möchte Tests für construct_url und scrape_url schreiben. Was ist der sinnvollste Weg, dies zu tun?

Einige Ansätze, die ich in Betracht gezogen habe:

  • Verschieben Sie die Funktionen in eine Dienstprogrammdatei und schreiben Sie Tests für diese Dienstprogrammdatei in eine Standard-Python-Testbibliothek. Möglicherweise die beste Option, obwohl nicht der gesamte Code im Notizbuch sichtbar ist.
  • Schreiben Sie Asserts mithilfe von Testdaten in das Notebook selbst (erhöht das Rauschen des Notebooks).
  • Verwenden Sie spezielle Jupyter-Tests, um den Inhalt der Zellen zu testen (glauben Sie nicht, dass dies funktioniert, da sich der Inhalt der Zellen ändern wird).
15
Richard

Es ist möglich, Python-Standardtesttools wie doctest oder unittest direkt im Notebook zu verwenden.

Doctest

Eine Notebook-Zelle mit einer Funktion und einem Testfall in einem Dokumentstring:

def add(a, b):
    '''
    This is a test:
    >>> add(2, 2)
    5
    '''
    return a + b

Eine Notizbuchzelle (die letzte im Notizbuch), in der alle Testfälle in den Dokumentzeichenfolgen ausgeführt werden:

import doctest
doctest.testmod(verbose=True)

Ausgabe:

Trying:
    add(2, 2)
Expecting:
    5
**********************************************************************
File "__main__", line 4, in __main__.add
Failed example:
    add(2, 2)
Expected:
    5
Got:
    4
1 items had no tests:
    __main__
**********************************************************************
1 items had failures:
   1 of   1 in __main__.add
1 tests in 2 items.
0 passed and 1 failed.
***Test Failed*** 1 failures.

Gerätetest

Eine Notebook-Zelle mit einer Funktion:

def add(a, b):
    return a + b

Eine Notebook-Zelle (die letzte im Notebook), die einen Testfall enthält. Die letzte Zeile in der Zelle führt den Testfall aus, wenn die Zelle ausgeführt wird:

import unittest

class TestNotebook(unittest.TestCase):

    def test_add(self):
        self.assertEqual(add(2, 2), 5)


unittest.main(argv=[''], verbosity=2, exit=False)

Ausgabe:

test_add (__main__.TestNotebook) ... FAIL

======================================================================
FAIL: test_add (__main__.TestNotebook)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "<ipython-input-15-4409ad9ffaea>", line 6, in test_add
    self.assertEqual(add(2, 2), 5)
AssertionError: 4 != 5

----------------------------------------------------------------------
Ran 1 test in 0.001s

FAILED (failures=1)

Fehlerbehebung bei einem fehlgeschlagenen Test

Während des Debuggens eines fehlgeschlagenen Tests ist es oft nützlich, die Testfallausführung zu einem bestimmten Zeitpunkt anzuhalten und einen Debugger auszuführen. Fügen Sie dazu den folgenden Code direkt vor der Zeile ein, an der die Ausführung angehalten werden soll:

import pdb; pdb.set_trace()

Zum Beispiel:

def add(a, b):
    '''
    This is the test:
    >>> add(2, 2)
    5
    '''
    import pdb; pdb.set_trace()
    return a + b

In diesem Beispiel wird die Ausführung beim nächsten Ausführen von doctest unmittelbar vor der return-Anweisung angehalten und der Python-Debugger (pdb) wird gestartet. Sie erhalten eine pdb-Eingabeaufforderung direkt im Notizbuch, mit der Sie die Werte von a und b überprüfen, über Zeilen springen usw.

Ich habe ein Jupyter-Notizbuch zum Experimentieren erstellt mit den Techniken, die ich gerade beschrieben habe.

14

Meiner Meinung nach ist der beste Weg, einen Unit-Test in einem Jupyter-Notebook durchzuführen, das folgende Paket: https://github.com/JoaoFelipe/ipython-unittest

beispiel aus dem Paket docs:

%%unittest_testcase
def test_1_plus_1_equals_2(self):
    sum = 1 + 1
    self.assertEqual(sum, 2)

def test_2_plus_2_equals_4(self):
    self.assertEqual(2 + 2, 4)

Success
..
----------------------------------------------------------------------
Ran 2 tests in 0.000s

OK
1
Michael D

In Anbetracht Ihres Kontexts ist es am besten, doctests für construct_url & scrape_url Innerhalb von Notizbuchzellen wie dieser zu schreiben.

def construct_url(data):
    '''
    >>> data = fetch_test_data_from_somewhere()
    >>> construct_url(data)
    'http://some-constructed-url/'
    '''

    ... 
    <actual function>
    ...

Dann können Sie sie mit einer anderen Zelle unten ausführen:

import doctest
doctest.testmod(verbose=True)

Ich habe auch treon erstellt, eine Testbibliothek für Jupyter-Notizbücher, mit der Sie Doctests und Unittests in Notizbüchern ausführen können. Es kann auch Notebooks von oben nach unten in einem neuen Kernel ausführen und alle Ausführungsfehler melden (Sanity Testing).

0
amirathi

Nachdem ich ein bisschen recherchiert hatte, kam ich zu meiner eigenen Lösung, in der mein eigener Testcode so aussieht

def red(text):
    print('\x1b[31m{}\x1b[0m'.format(text))

def assertEquals(a, b):
    res = a == b
    if type(res) is bool:
        if not res:
            red('"{}" is not "{}"'.format(a, b))
            return
    else:
        if not res.all():
            red('"{}" is not "{}"'.format(a, b))
            return

    print('Assert okay.')

Was es tut, ist

  • Überprüfen Sie, ob ab entspricht.
  • Wenn sie unterschiedlich sind, werden die Argumente in Rot angezeigt.
  • Wenn sie gleich sind, heißt es "okay".
  • Wenn das Ergebnis des Vergleichs ein Array ist, wird geprüft, ob all() wahr ist.

Ich habe die Funktion auf mein Notebook gelegt und teste so etwas

def add(a, b):
    return a + b

assertEquals(add(1, 2), 3)
assertEquals(add(1, 2), 2)
assertEquals([add(1, 2), add(2, 2)], [3, 4])

---

Assert okay.
"3" is not "2"  # This is shown in red.
Assert okay.

Vorteile dieses Ansatzes sind

  • Ich kann Zelle für Zelle testen und das Ergebnis sehen, sobald ich eine Funktion ändere.
  • Ich muss keinen zusätzlichen Code wie doctest.testmod(verbose=True) hinzufügen, den ich hinzufügen muss, wenn ich doctest verwende.
  • Fehlermeldungen sind einfach.
  • Ich kann meinen Testcode (Assert-Code) anpassen.
0
Sanghyun Lee