web-dev-qa-db-fra.com

Tests unitaires des fonctions dans un ordinateur portable Jupyter?

J'ai un ordinateur portable Jupyter que je compte utiliser à plusieurs reprises. Il a des fonctions, la structure du code est la suivante:

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

J'aimerais écrire des tests pour construct_url et scrape_url. Quelle est la manière la plus sensée de faire cela?

Quelques approches que j'ai envisagées:

  • Déplacez les fonctions dans un fichier d’utilitaire et écrivez des tests pour ce fichier dans une bibliothèque de tests Python standard. Peut-être la meilleure option, même si cela signifie que tout le code n'est pas visible dans le bloc-notes.
  • Écrire des affirmations dans le bloc-notes lui-même, en utilisant des données de test (ajoute du bruit au bloc-notes).
  • Utilisez les tests Jupyter spécialisés pour tester le contenu des cellules (ne pensez pas que cela fonctionne, car le contenu des cellules va changer).
15
Richard

Il est possible d’utiliser des outils de test standard Python, tels que doctest ou unittest , directement dans le cahier.

Docteur

Une cellule de bloc-notes avec une fonction et un scénario de test dans une docstring:

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

Une cellule de bloc-notes (la dernière dans le bloc-notes) qui exécute tous les tests dans les docstrings:

import doctest
doctest.testmod(verbose=True)

Sortie:

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.

Test de l'unité

Une cellule de cahier avec une fonction:

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

Une cellule de bloc-notes (la dernière dans le bloc-notes) contenant un scénario de test. La dernière ligne de la cellule exécute le scénario de test lors de l'exécution de la cellule:

import unittest

class TestNotebook(unittest.TestCase):

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


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

Sortie:

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)

Débogage d'un test ayant échoué

Lors du débogage d'un test ayant échoué, il est souvent utile d'interrompre l'exécution du scénario de test à un moment donné et d'exécuter un débogueur. Pour cela, insérez le code suivant juste avant la ligne sur laquelle vous voulez que l'exécution s'arrête:

import pdb; pdb.set_trace()

Par exemple:

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

Pour cet exemple, la prochaine fois que vous exécuterez le test, l’exécution s’arrêtera juste avant l’instruction return et le débogueur Python (pdb) démarrera. Vous obtiendrez une invite pdb directement dans le bloc-notes, ce qui vous permettra d'inspecter les valeurs de a et b, des lignes successives, etc.

J'ai créé un Jupyter notebook pour expérimenter avec les techniques que je viens de décrire.

14
SergiyKolesnikov

À mon avis, le meilleur moyen d’avoir un test unitaire dans le cahier Jupyter est le package suivant: https://github.com/JoaoFelipe/ipython-unittest

exemple tiré du paquet 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

Après des recherches un peu, j'ai atteint ma propre solution où j'ai mon propre code de test qui ressemble à ceci

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

Qu'est-ce qu'il fait est

  • Vérifiez si a est égal à b.
  • S'ils sont différents, les arguments sont en rouge.
  • S'ils sont identiques, ils disent «d'accord».
  • Si le résultat de la comparaison est un tableau, il vérifie si all() est vrai.

Je mets la fonction en haut de mon cahier et je teste quelque chose comme ça

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.

Les avantages de cette approche sont

  • Je peux tester cellule par cellule et voir le résultat dès que je modifie quelque chose d'une fonction.
  • Je n'ai pas besoin d'ajouter de code supplémentaire, comme doctest.testmod(verbose=True), que je dois ajouter si j'utilise doctest.
  • Les messages d'erreur sont simples.
  • Je peux personnaliser mon code de test (assert).
0
Sanghyun Lee