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:
Es ist möglich, Python-Standardtesttools wie doctest oder unittest direkt im Notebook zu verwenden.
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.
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)
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.
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
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).
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
a
b
entspricht.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
doctest.testmod(verbose=True)
hinzufügen, den ich hinzufügen muss, wenn ich doctest verwende.