En informatique, un test désigne une procédure de vérification partielle d'un système. Son objectif principal est d'identifier un nombre maximal de comportements problématiques du logiciel. Il permet ainsi, dès lors que les problèmes identifiés seront corrigés, d'en augmenter la qualité.
-- wikipedia
Le plus de choses un test vérifie, le moins le test veut dire quelque chose
Le plus de choses un test vérifie, le plus façile il est à écrire
Monsieur le garagiste, je n'ai pas réussit à aller à l'épicerie avec ma voiture aujourd'hui. Je pense qu'elle est brisé. Pouvez-vous la réparer, si oui, quand sera-t-elle réparée ?
-- didier, la première fois qu'il ira au garagiste
En informatique, le test d'acceptation (ou recette) est une phase de développement des projets, visant à assurer formellement que le produit est conforme aux spécifications (réponse donnée à un instant « t » aux attentes formulées). Elle s'inscrit dans les activités plus générales de qualification.
Executé juste avant de déployer en production
Test plusieurs systèmes
Test très vaste
D'habitude fait par les QAs
Les tests système de logiciel ou de matériel réfèrent à un processus de test d'un système intégré afin d'évaluer sa conformité aux exigences spécifiées.
Test 1 système de bout-en-bout
Même chose qu'un système d'acceptation mais sur 1 système
D'habitude fait par les QAs
Vérifie le bon fonctionnement d'une partie précise d'un logiciel ou d'une portion d'un programme (appelée « unité » ou « module ») ; dans le test d’intégration, chacun des modules indépendants du logiciel est assemblé et testé dans l’ensemble.
En programmation informatique, le test unitaire est une procédure permettant de vérifier le bon fonctionnement d'une partie précise d'un logiciel ou d'une portion d'un programme (appelée « unité » ou « module »).
Écrire un test de la fonction suivante
def is_balanced(text: str) -> bool:
counter = 0
for letter in text:
if letter == "(":
counter += 1
if letter == ")":
counter -= 1
if counter < 0:
return False
return counter == 0
def test_is_balances():
assert is_balanced("")
assert is_balanced("()")
assert is_balanced("(()())")
assert not is_balanced("((())")
assert not is_balanced("(()))")
assert not is_balanced(")")
Écrire un test de la fonction suivante
def is_balanced_file(file_path: Path) -> bool:
with open(file_path, "r") as file_:
text = file_.read()
counter = 0
for letter in text:
if letter == "(":
counter += 1
if letter == ")":
counter -= 1
if counter < 0:
return False
return counter == 0
def write_to_tmp_file(tmp_dir: Path, content: str) -> Path:
tmp_file = tmp_dir / "foo.txt"
tmp_file.write_text(content)
return tmp_dir
def test_is_balanced_file(tmp_path):
tmp_path = Path(tmp_path)
assert is_balanced_file(write_to_tmp_file(tmp_path, ""))
assert is_balanced_file(write_to_tmp_file(tmp_path, "()"))
assert is_balanced_file(write_to_tmp_file(tmp_path, "(()())"))
assert not is_balanced_file(write_to_tmp_file(tmp_path, "((())"))
assert not is_balanced_file(write_to_tmp_file(tmp_path, "(()))"))
assert not is_balanced_file(write_to_tmp_file(tmp_path, ")"))
Ceci est un test d'intégration
Le test dépend de fichiers
premièrement, on change la fonction
def is_balanced_file_object(file_path: typing.IO[str]) -> bool:
text = file_path.read()
counter = 0
for letter in text:
if letter == "(":
counter += 1
if letter == ")":
counter -= 1
if counter < 0:
return False
return counter == 0
On test avec une version inmemory
def test_is_balanced_file_object() -> None:
assert is_balanced_file_object(io.StringIO(""))
assert is_balanced_file_object(io.StringIO("()"))
assert is_balanced_file_object(io.StringIO("(()())"))
assert not is_balanced_file_object(io.StringIO("((())"))
assert not is_balanced_file_object(io.StringIO("(()))"))
assert not is_balanced_file_object(io.StringIO(")"))
On change la fonction
is_balanced_filepour utiliser la fonctionis_balancedOn ne test pas la méthodeis_balanced_file
def is_balanced_file(file_path: Path) -> bool:
with open(file_path, "r") as file_:
return is_balanced(file_.read())
Le code coverage du résultat sera plus bas
Est-ce vraiment grave? didier pense que non
Écrire une fonction qui prend en argument une liste d'entiers lst et un entier target. La fonction doit retourner 2 nombres de la lst (possiblement deux fois le même nombre) a et b tel que a + b = target.
Tester la fonction
https://leetcode.com/problems/two-sum/
lst = [55, 20, 76, 34, 80, 26]
target = 100
result = (20, 80)
Écrire une fonction qui prend un string en paramêtre et qui retourne le plus long "sous-string" qui est un palindrome
Tester la fonction
https://leetcode.com/problems/longest-palindromic-substring/
input_ = "la plus belle ville au monde est laval."
result = "laval"
En utilisant le module random de python, écrire une fonction qui retoure un mouvement brownien de longeur $n$ et tester le.
Un mouvement brownien est une liste de nombre où le premier item est $0$ et l'item $n+1$ est définit comme la valeur de l'item $n$ plus une valeur aléatoire provenant d'une normale de moyenne 0 et d'écart-type 1.
%pylab inline
from random import Random
def brownian_movement(n, random):
ret = [1]
for i in range(n - 1):
ret.append(ret[-1] + random.gauss(0, 1))
return ret
plot(list(range(250)), brownian_movement(250, Random(80)))
None
%pylab is deprecated, use %matplotlib inline and import the required libraries. Populating the interactive namespace from numpy and matplotlib
Écrire une fonction qui prend le nom d'un fichier et qui compte le nombre de ligne dans le fichier
Écrire un teste
# Solution
def count_number_of_line(filename: str) -> int:
with open(filename, "rt") as file_:
return file_.read().count("\n")
Ce qui se passe avant un test se nomme souvant "setup", "beforeEach"
Si nous ne sommes pas capable de rouler le "setup", le test doit échouer
Ce qui se passe après le teste se nomme souvant "tearDown", "afterEach"
Il est important que ce qui se passe après le test soit toujours executé!
Pytest utilise un manière un peu particulière pour gérer les setup & teardown
Chaque setup et teardown est associé à une "chose": la fixture
Le code qui génère une fixture est un "setup"
Le code qui est roulé après une fixture est un "teardown" (avancé)
Les arguments des fonctions tests sont des fixtures
Pytest inclue des fixtures https://docs.pytest.org/en/6.2.x/fixture.html
La fixture la plus utile est https://docs.pytest.org/en/6.2.x/reference.html#std-fixture-tmp_path
from pathlib import Path
def test_copy_file(tmp_path: Path) -> None:
...
import pytest
@pytest.fixture
def odd_number() ->:
return 2 * 25231
def test_is_odd(odd_number) -> None:
assert is_odd(odd_number)
assert not is_odd(odd_number + 1)
import pytest
@pytest.fixture
def odd_number() -> number:
return 2 * 25231
@pytest.fixture
def even_number(odd_number) -> number:
return odd_number + 1
def test_is_odd(even_number) -> None:
assert not is_odd(even_number)
assert is_odd(even_number - 1)
Écrire un programme qui prend le nom d'un fichier JSON personnes et qui retourne le nombre d'homme dans le fichier.
Tester la fonction en utilisant des fixtures
exemple d'input
[
{"name": "paulette", "sexe": "femme"},
{"name": "claudette", "sexe": "homme"},
{"name": "paul", "sexe": "homme"},
{"name": "herménégilde", "sexe": "homme"},
]
Écrire un programme qui prend deux nom de fichiers et qui vérifie si les deux fichiers ont le même contenue
Tester la fonction en utilisant des fixtures
Écrire une fonction qui prend un Player et qui ajoute à un fichier une ligne avec la personne sérialisé en format {prenom} {nom de famille} - {équipe}
Tester la fonction en utilisant des fixtures
class Player:
def __init__(self, first_name, last_name, team):
self.first_name = fist_name
self.last_name = last_name
self.team = team
def serialize_player_to_file(player: Player, filename: Path) -> None:
pass
La fonction ne fait que du input/output
La fonction dépend de libraries merdiques
Il serait compliquer de tout découpler
Écrire une fonction qui prend en argument un url ainsi qu'un nom de fichier et qui télécharge l'url dans le fichier.
Tester la fonction
truc
utiliser l'url https://httpbin.org/get
utiliser la librarie requests
il n'y a pas de trucs ici, il faut faire du IO :(
Utilisé du IO mais très restraint (pas toujours possible)
Mocker
Python possède une des meilleur libraries pour faire des mocks
On a vue qu'écrire des tests est difficile.
Les fonctions doivent faire une et une unique chose
Les fonctions doivent utiliser la notion la notion d'Inversion des dépendances
Oui! En écrivant les tests avant d'écrire le code.
Développé par Kent Beck
Provien du début des années 2000
Est un accronyme vraiment important à ajouter à votre CV
Nous allons avoir un ensemble de test qui vérifie parfaitement le logiciel
Le code sera testable dès le jour 1
Le project sera toujour à 15 minutes d'être près pour la production
Nous allons avoir des exemples de chanques fonction dans le code
Nous allons utiliser très peu de mocks
Les tests seront aussi beau que le code de base