Les tests unitaires sont l'élément clé pour créer un programme fiable. Il est impensable de s'en passer. Un test unitaire est une fonction qui s'assure qu'une autre fonction retourne le résultat souhaité pour les mêmes entrées. Ils sont présents dans tous les langages.
Les modules python les plus utilisés sont aussi les plus testés, ils sont validés par des milliers de tests unitaires.
def unit_test():
y = f(x)
if y != valeur_attendue:
raise AssertionError(f"{y} != {valeur_attendue}")
from jyquickhelper import add_notebook_menu
add_notebook_menu()
On suppose qu'une tour est placée sur un échiquier, on veut savoir combien de coups il faut pour atteindre une autre case.
def tour_prend_piece(x1, y1, x2, y2):
# ...
return 1 or 2
Si votre fonction est bien correct, la fonction suivante doit s'exécuter sans erreur.
def test_tour_prend_piece():
assert tour_prend_piece(0, 0, 0, 1) == 1
assert tour_prend_piece(0, 0, 1, 0) == 1
assert tour_prend_piece(1, 0, 0, 0) == 1
assert tour_prend_piece(0, 1, 0, 0) == 1
assert tour_prend_piece(0, 0, 1, 1) == 2
assert tour_prend_piece(0, 2, 1, 1) == 2
La fonction précédente a quatre arguments. On souhaite les remplacer par deux tuple.
def tour_prend_piece_tuple(t1, t2):
# ...
return True or False
def test_tour_prend_piece_tuple():
def _tour_prend_piece(x1, y1, x2, y2):
return tour_prend_piece_tuple((x1, y1), (x2, y2))
assert _tour_prend_piece(0, 0, 0, 1) == 1
assert _tour_prend_piece(0, 0, 1, 0) == 1
assert _tour_prend_piece(1, 0, 0, 0) == 1
assert _tour_prend_piece(0, 1, 0, 0) == 1
assert _tour_prend_piece(0, 0, 1, 1) == 2
assert _tour_prend_piece(0, 2, 1, 1) == 2
Ecrire une fonction qui prend en compte les obstacles : la tour ne peut pas traverser une case si une pièce est présente. On pourra s'inspirer d'un algorithme de coloriage. Qu'en est-il des tests unitaires précédents ?
L'idée est colorier l'échiquier avec le nombre de coups qu'il faut pour atteindre une case.
import numpy
def find_neighbour(echiquier, p):
x, y = p
if x > 0 and echiquier[x-1, y] == -1:
return (x-1, y), (-1, 0)
if x < echiquier.shape[0] - 1 and echiquier[x+1, y] == -1:
return (x+1, y), (1, 0)
if y > 0 and echiquier[x, y-1] == -1:
return (x, y-1), (0, -1)
if y < echiquier.shape[1] - 1 and echiquier[x, y+1] == -1:
return (x, y+1), (0, 1)
return None, None
def coloring(t1, t2, obstacles):
obstacles = set(obstacles) # pour aller plus vite
echiquier = numpy.zeros((8, 8)) -1
for obs in obstacles:
echiquier[obs] = -2
echiquier[t1] = 0
cases = [t1]
while len(cases) > 0:
case = cases[0]
next_case, direction = find_neighbour(echiquier, case)
if next_case is None:
del cases[0]
continue
x, y = next_case
value = echiquier[case] + 1
while x >= 0 and y >= 0 and x < echiquier.shape[0] and y < echiquier.shape[1]:
if echiquier[x, y] == -2:
break
if echiquier[x, y] == -1:
echiquier[x, y] = value
else:
echiquier[x, y] = min(value, echiquier[x, y])
cases.append((x, y))
x += direction[0]
y += direction[1]
return echiquier[t2]
coloring((0, 0), (6, 6), [(0, 6), (6, 0), (1, 5), (5, 1), (5, 6), (5, 5), (6, 5)])
4.0
def tour_prend_piece_obstacle(t1, t2, obstacles):
# ...
return # ...
On considère que l'échiquier est de taille connue mais plus nécessairement 8x8. Modifier la fonction pour prendre en compte ce changement. Qu'en est-il des tests unitaires.
Les tests unitaires :
test_
.Des milliers de tests unitaires :
pytest <répertoire>
cherche toutes les fonctions commencençant par test_
et les exécute.Intégration continue :
Exemple avec scikit-learn, résultats des tests scikit-learn/build
def tour_prend_piece_obstacle(t1, t2, obstacles):
if min(t1) < 0 or min(t2) < 0:
raise ValueError(f"Une pièce est en dehors de l'échiquier, pièces : {t1} ou {t2}.")
return # ...
def test_tour_prend_piece_obstacle_exception():
# ...
pass