from IPython.display import HTML, Image
HTML('''<script>
code_show=true;
function code_toggle() {if (code_show){$('div.input').hide();} else {$('div.input').show();}code_show = !code_show}
$( document ).ready(code_toggle);
</script>
<form action="javascript:code_toggle()"><input type="submit"
value="Cliquer ici pour cacher / afficher le code brut."></form>''')
François HU - 01/10/19 - séance 5
from jyquickhelper import add_notebook_menu
add_notebook_menu()
faire des tests (dans un programme test_*.py ou *_test.py par convention) pour localiser la source d’erreur dans un programme (exemple de package : unittest, pytest)
Image("img/unittest.png", width=700, height = 200)
from math import pi
def circle_area(r):
return(pi*(r**2))
# test fonction
message = "Aire du cercle de rayon r = {rayon} est {aire}"
for r in [2, -3, 2+5j, True, "radius"]:
A = circle_area(r)
print(message.format(rayon = r, aire = A))
Aire du cercle de rayon r = 2 est 12.566370614359172 Aire du cercle de rayon r = -3 est 28.274333882308138 Aire du cercle de rayon r = (2+5j) est (-65.97344572538566+62.83185307179586j) Aire du cercle de rayon r = True est 3.141592653589793
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-8-c0d4d58d2528> in <module> 2 message = "Aire du cercle de rayon r = {rayon} est {aire}" 3 for r in [2, -3, 2+5j, True, "radius"]: ----> 4 A = circle_area(r) 5 print(message.format(rayon = r, aire = A)) <ipython-input-7-886f03e67d62> in circle_area(r) 2 3 def circle_area(r): ----> 4 return(pi*(r**2)) TypeError: unsupported operand type(s) for ** or pow(): 'str' and 'int'
import unittest
class TestCircleArea(unittest.TestCase):
def test_area(self):
# test aires lorsque rayon >= 0
self.assertAlmostEqual(circle_area(1), pi)
self.assertAlmostEqual(circle_area(0), 0)
self.assertAlmostEqual(circle_area(1.1), pi * 1.1**2)
! python -m unittest
. ---------------------------------------------------------------------- Ran 1 test in 0.000s OK
class TestCircleArea(unittest.TestCase):
def test_area(self):
# test aires lorsque rayon >= 0
self.assertAlmostEqual(circle_area(1), pi)
self.assertAlmostEqual(circle_area(0), 0)
self.assertAlmostEqual(circle_area(1.1), pi * 1.1**2)
def test_values(self):
# test aires lorsque rayon <0
self.assertRaises(ValueError, circle_area, -2)
def test_types(self):
# test aires lorsque rayon n'est pas un 'int'
self.assertRaises(ValueError, circle_area, 3+5j)
self.assertRaises(ValueError, circle_area, True)
self.assertRaises(ValueError, circle_area, "radius")
#unittest.main(argv=[''], verbosity=2, exit=False)
! python -m unittest
.FF
======================================================================
FAIL: test_types (test_my_program.TestCircleArea)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/Users/Faugon/Documents/3_enseignements/algo_programmation/cours_5/test_my_program.py", line 17, in test_types
self.assertRaises(ValueError, circle_area, 3+5j)
AssertionError: ValueError not raised by circle_area
======================================================================
FAIL: test_values (test_my_program.TestCircleArea)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/Users/Faugon/Documents/3_enseignements/algo_programmation/cours_5/test_my_program.py", line 13, in test_values
self.assertRaises(ValueError, circle_area, -2)
AssertionError: ValueError not raised by circle_area
----------------------------------------------------------------------
Ran 3 tests in 0.001s
FAILED (failures=2)
from math import pi
def circle_area(r):
if type(r) not in [int, float]:
raise ValueError("le rayon doit etre un entier positif")
if r < 0:
raise ValueError("le rayon ne peut pas etre negatif")
return(pi*(r**2))
#unittest.main(argv=[''], verbosity=2, exit=False)
! python -m unittest
---------------------------------------------------------------------- Ran 0 tests in 0.000s OK
import pytest
def test_area():
# test aires lorsque rayon >= 0
circle_area(1) == pi
circle_area(0) == 0
circle_area(1.1) == pi * 1.1**2
def test_values():
# test aires lorsque rayon <0
with pytest.raises(ValueError):
circle_area(-2)
def test_types():
# test aires lorsque rayon n'est pas un 'int'
with pytest.raises(ValueError):
circle_area(3+5j)
circle_area(True)
circle_area("radius")
! py.test test_my_program.py
============================= test session starts ============================== platform darwin -- Python 3.7.3, pytest-5.2.1, py-1.8.0, pluggy-0.13.0 rootdir: /Users/Faugon/Documents/3_enseignements/algo_programmation/cours_5 collected 3 items test_my_program.py ... [100%] ============================== 3 passed in 0.01s ===============================
ou
! py.test
============================= test session starts ============================== platform darwin -- Python 3.7.3, pytest-5.2.1, py-1.8.0, pluggy-0.13.0 rootdir: /Users/Faugon/Documents/3_enseignements/algo_programmation/cours_5 collected 3 items test_my_program.py ... [100%] ============================== 3 passed in 0.06s ===============================
désigne une astuce uilisée pour écrire un test unitaire pour une fonctionnalité utilisant une ressource externe comme internet (exple de package : unittest.mock, **)
Image("img/mocking.png", width=700, height = 200)
mesure le nombre de lignes de code effectivement executée lors de l’exécution des tests unitaires. (objectif env. 80%. exple : module coverage pour mesurer la couverture)
Image("img/coverage.png", width=600, height = 200)
Facile d’augmenter la couverture pour des fonctions de calculs numériques
Difficile pour un processus d’automatisation qui requiert l’accès à une ressource externe
au lieu de faire :
$ python test_my_program.py arg1 arg2
on fait :
$ coverage run test_my_program.py arg1 arg2
ensuite :
$ coverage report
ou :
$ coverage report -m
l'option "-m" identifie la ou les lignes posant problème
pour une présentation plus propre :
coverage html
voici notre programme et notre programme-test :
Image("img/my_program.png", width=650, height = 200)
! coverage report test_my_program.py
Name Stmts Miss Cover ---------------------------------------- test_my_program.py 15 0 100%
Permet de mesurer la vitesse d’exécution et la consommation de chaque fonction ou chaque ligne. Quand est-ce qu'on utilise un profiling ? le programme est lent et on veut savoir pourquoi.
Image("img/profiling.png", width=580, height = 200)
from IPython.display import IFrame
IFrame("http://www.xavierdupre.fr/app/ensae_teaching_cs/helpsphinx3/notebooks/profiling_example.html#profilingexamplerst",
width=920, height=550)
Une application (web p.ex.) est souvent déployée sur plusieurs machines. Une entreprise dispose en générale de plusieurs environnements de test afin de pouvoir tester une application ou un service en grandeur réelle sans l’exposer aux utilisateurs. (exple : voir ansible, automatisation facile et rapide)
Tous les tests présentés ci-dessus ne font que tester un cas particulier. La fonction marche avec ce jeu de données dans ce cas précis. Le test ne vérifie pas que la fonction est valide dans tous les cas. Cela ne peut passer que part une preuve formelle que le code fonctionne. Dans certains cas, cette preuve formelle est nécessaire car toute erreur a un coût prohibitif. Les logiciels de calculs utilisée par Ariane Espace le sont. On rentre dans le domaine des mathématiques
Logger consiste à enregister des événements, des opérations effectués par un programme dans le but de les analyser plus tard en cas d’erreur.
Image("img/logging1.png", width=500, height = 200)
Image("img/logging2.png", width=700, height = 200)
import logging
from math import sqrt
# creer et configurer notre logger
LOG_FORMAT = "%(levelname)s %(asctime)s - %(message)s"
logging.basicConfig(filename = "process.log", level = logging.DEBUG, format = LOG_FORMAT, filemode = "w")
logger = logging.getLogger()
# tester le logger
def quadratic_formula(a,b,c):
"""Retourne la solution de l'equation ax^2 + bx + c = 0"""
logger.info("quadratic_formula({0},{1},{2})".format(a,b,c))
# calcul du discriminant
disc = b**2 - 4*a*c
# calcul des 2 racines
logger.debug("# calcul des 2 racines")
root1 = (-b + sqrt(disc)) / (2*a)
root2 = (-b - sqrt(disc)) / (2*a)
# retourne les racines
logger.debug("# retourne les racines")
return(root1, root2)
roots = quadratic_formula(1,0,-4)
print(roots)
(2.0, -2.0)
Image("img/exemple_logging1.png", width=700, height = 200)
roots = quadratic_formula(1,0,1)
--------------------------------------------------------------------------- ValueError Traceback (most recent call last) <ipython-input-2-41dffaa824fe> in <module> ----> 1 roots = quadratic_formula(1,0,1) <ipython-input-1-090c10bbe367> in quadratic_formula(a, b, c) 17 # calcul des 2 racines 18 logger.debug("# calcul des 2 racines") ---> 19 root1 = (-b + sqrt(disc)) / (2*a) 20 root2 = (-b - sqrt(disc)) / (2*a) 21 ValueError: math domain error
Image("img/exemple_logging2.png", width=700, height = 200)
niveaux de logging : Debug, Info, Warning, Error, Critical
L’outil standard en Python est Sphinx. Il aboutit à une documentation statique HTML.
voici le projet qui peut servir de modèle pour construire un module qui implémente une stratégie 2048 : https://github.com/sdpython/pystrat2048
Quand on génère la documentation avec sphinx : http://www.xavierdupre.fr/app/pystrat2048/helpsphinx/index.html
Le langage Python est faiblement typé. On précise parfois le type attendu dans la documentation d’une fonction ou d’une classe. Les annotations sont un moyen formel de le faire. Même si elle n’a aucune incidence, cette information peut être utilisée
def foo(prefix, suffix):
"""
prefix: premier mot
suffix: dernier mot
returns : les deux mots avec "et" entre les deux
"""
return prefix + " et " + suffix
res = foo("manger", "boire")
print(res)
manger et boire
help(foo)
Help on function foo in module __main__:
foo(prefix, suffix)
prefix: premier mot
suffix: dernier mot
retourne : les deux mots avec "et" entre les deux
cependant on nous donne que seulement un string
def foo(prefix: "premier mot",
suffix: "dernier mot") -> "les deux mots avec 'et' entre les deux":
return prefix + " et " + suffix
res = foo("manger", "boire")
print(res)
print()
help(foo)
manger et boire Help on function foo in module __main__: foo(prefix: 'premier mot', suffix: 'dernier mot') -> "les deux mots avec 'et' entre les deux"
from pprint import pprint
pprint(foo.__annotations__)
{'prefix': 'premier mot',
'return': "les deux mots avec 'et' entre les deux",
'suffix': 'dernier mot'}
def foo(prefix: str,
suffix: str) -> str:
return prefix + " et " + suffix
res = foo("manger", "boire")
print(res)
print()
help(foo)
manger et boire Help on function foo in module __main__: foo(prefix: str, suffix: str) -> str
Il est plus facile de lire un code qui suit toujours les mêmes règles d’écriture. En Python, on les appelles PEP8. Ce ne sont pas des règles à suivre à la lettre mais la plupart des développeurs python les suivent. Quelques conventions :
ne pas dépasser 79 caractères par ligne de code ...
sauter deux lignes entre les fonctions et les classes (mais une ligne entre les méthodes d'une classe)
variables_regulieres : en minuscule avec des "_" si besoin
CONSTANTES : tout en majuscule
nom_fonction() : en minuscule avec des "_" si besoin
ClassNames : Majuscule pour chaque début de mot
_les_proprietes_non_publics : "_" au debut
conflitnom : "_" a la fin si il y a des conflits de nom
En terme de design, scikit-learn est un modèle du genre. La librairie propose des algorithmes de machine learning - ce n’est pas nouveau - mais son adoption rapide est due à la simplicité de son interface, de son design.
C’est devenu un outil incontournable pour garder la trace des modifications apporter à un programme. La première tâche est la possiblité de revenir en arrière. Aujourd’hui, la plupart des nouveaux projets commencent sur git. Voici les trois sites suivants qui hébergent gratuitement les projets open source :
GitHub
GitLab
Bitbucket
Le terme consacré est KanBan. Il est issu des méthodes agiles. Concrètement, plus on en nombreux à travailler sur le même projet, plus il est difficile de garder la trace de ce que chacun fait. Cette approche a été en quelque sorte validée par la pratique.
Une revue de code intervient avant la mise à jour du code d’un logiciel. C’est l’occasion pour un dévelopeur de partager ses modifications avec le reste de son équipe qui commentent les parties du code qui leur déplaisent ou qu’ils approuvent si la mise à jour leur convient. La règle est souvent qu’une modification ne peut être prise en compte dans le code de l’application que si un ou deux autres dévelopeurs la valide.
Poussé à l’extrême, cela devient le pair programming qui consiste à programmer à deux devant le même écran. C’est assez cauchemardesque si c’est permanent. En pratique, la revue de code est un exercice utile. Un logiciel open source : gerrit.
On devrait faire tourner l’ensemble des tests unitaires à chaque modification. En pratique, on le fait pas toujours voire rarement car cela prend trop de temps. On délègue cette tâche à une machine distante (voir les projets open source travis sur Linux, Circle CI, appveyor sur Windows. Localement, on peut utiliser Jenkins qui est très simple d’utilisation ou BuildBot).