class: center, middle # Tester en Python ## [Arthur Vuillard](mailto:arthur@hashbang.fr) --- # Tester ? .w75.centered-block[ 1. Valider 2. Revalider plus tard 3. Éviter les régressions 4. Corriger un bug 5. Automatiser l'essai lors du dev ] ??? 1. Valider que le code correspond à ce que vous envisagiez 2. Revalider plus tard, après que d'autres devs ait été faits 3. Quand le code évolue, valider qu'une fonction continue à faire ce pour quoi elle a été prévue 4. Reproduire un bug, aider à le corriger 5. Permettre de faire des essais automatiquement plutôt que d'essayer à la main --- # Que tester ? .w50.centered-block[ - * - bugs - cas limites - éléments extérieurs ] ??? Chaque bout de code que vous jugerez utile de tester Tout dépend de vos exigences en terme de qualité --- class: center, middle # Comment ? --- # Code à tester .w50.centered-block.fs32[ ```python def add(a, b): return a + b ``` ] --- class: center, middle # Doctest --- # Doctest : exemple ```python def add(a, b): '''Return the sum of the numbers >>> add(1, 2) 3 ''' return a + b ``` ??? Écriture d'un test dans la documentation d'un élément de code --- # Doctest : lancer ```sh python -m doctest add.py -v ``` ou, dans le code ```python if __name__ == "__main__": import doctest doctest.testmod() ``` ```sh python add.py ``` --- class: center, middle # Unittest --- # unittest : exemple test_add.py : ```python import unittest from add import add class TestAdd(unittest.TestCase): def test_float(self): result = add(2., 3) self.assertEqual(result, 5.) ``` --- # unittest : lancer ```sh python -m unittest test_add.py ``` ou dans test_add.py ```python if __name__ == "__main__": unittest.main() ``` et ```sh python test_add.py ``` --- #unittest : résultat ```sh . ------------------------------------------- Ran 1 test in 0.000s OK ``` --- #unittest : résultat ```sh F =========================================== FAIL: test_float (__main__.TestSum) ------------------------------------------- Traceback (most recent call last): File "test_add.py", line 7, in test_float self.assertEqual(result, 4.) AssertionError: 5.0 != 4.0 ------------------------------------------- Ran 1 test in 0.001s FAILED (failures=1) ``` --- class: center, middle # De l'intérêt des tests --- # De l'intérêt des tests ```python >>> add('4.0', 2) Traceback (most recent call last): File "
", line 1, in
File "./add.py", line 9, in add return a + b TypeError: Can't convert 'int' object to str implicitly ``` --- # De l'intérêt des tests ```python def add(a, b): '''Return the sum of the numbers >>> add(1, 2) 3 ''' return float(a) + float(b) ``` ```python >>> add('4.0', 2) 6.0 >>> add(1, 2) 3.0 ``` --- # De l'intérêt des tests ```sh $ python add.py **************************************** File "add.py", line 5, in __main__.add Failed example: add(1, 2) Expected: 3 Got: 3.0 **************************************** 1 items had failures: 1 of 1 in __main__.add ***Test Failed*** 1 failures. ``` --- class: center, middle # Lancez les tests ! --- # Évolution du code ```python def add_all(*args): current_sum = 0 for arg in args: current_sum = add(current_sum, arg) return current_sum ``` ??? - Tester add_all - add est déjà correctement testé --- class: center, middle # Mock --- # Mocking .w75.centered-block[ - remplacer une partie du système - comportement prévisible - simuler une API - simuler une ressource extérieur - simuler un comportement erratique - simuler une partie de code déjà valide ] ??? - REST, SOAP, HTTP... - Timeout, Could not connect, 40x, 50x - vérifier les appels d'un code à un autre ??? tests unitaires blabla ressources exterieures --- # Mocking .fs24[ ```python import mock class TestAddAll(unittest.TestCase): @mock.patch('add.add', return_value=1) def test_simple(self, mock_add): result = add_all(1) self.assertEqual(result, 1) mock_add.assert_called_once_with(0, 1) ``` ] --- class: center, middle # Aller plus loin --- # Aller plus loin : py.test - découverte automatique de tous les tests - lancement de tous les tests trouvés - simplifie l'écriture des tests ```python def test_the_obvious(): assert True == True ``` - fixtures - plugins pour différents frameworks - intégration serveurs d'intégration continue --- # Aller plus loin : coverage Outil pour mesurer la couverture du code par les tests unitaires ```sh $ coverage run -m pytest $ coverage report -m Name Stmts Miss Cover Missing ---------------------------------------- add 10 2 80% 19-20 test_add 14 1 93% 19 ---------------------------------------- TOTAL 24 3 88% ``` ??? Aussi branch coverage --- # Aller plus loin : tox Permet de tester sur plusieurs implémentations/versions de Python : ``` [tox] envlist = py27, py33, pypy, jython [testenv] commands = py.test deps = pytest ``` --- # Aller plus loin : intégration continue .w75.centered-block[ - lancer les tests - être alerté d'un échec - Buildbot/Jenkins/Whatever - SCM hooks ] --- # En résumé .centered-block.w85[ - C'est facile ! - Il suffit - écrire des tests - les lancer - Pour du code ancien : - écrire les tests pour les bugs - tester les fonctions de bas niveau ] --- class: center, middle # Des questions ?