Test unitaire

Objectif.

  • Savoir écrire un test unitaire simple.

Mise en situation

Une première approche des tests est celle du test unitaire. Il s'agit de tester chaque composant, chaque fonction de notre programme de manière isolée.

Imaginez un programme de gestion d'une association, et qui est donc en charge de plusieurs fonctionnalités : gérer les cotisations, la comptabilité, les dons effectués, etc. Le programme est composé de très nombreuses fonctions, comme par exemple une fonction qui calcule un abattement fiscal sur un don. L'idée du test unitaire sera d'écrire un test qui va se charger de lancer cette fonction plusieurs paramètres différents, et de s'assurer que le résultat est toujours celui que l'on attend.

RappelTest unitaire

Le but d'un test unitaire est de vérifier le bon fonctionnement d'une composante d'un programme, le plus souvent une fonction. Le test unitaire essaiera d'être exhaustif en vérifiant que chaque ligne de code est viable et ne provoque pas d'erreur. Par exemple, si une fonction contient une condition, il faudra au moins deux tests : un dans lequel la condition est vraie et l'autre dans lequel la condition est fausse.

Fondamental

On appellera test unitaire une fonction qui appelle de multiples fois une autre fonction afin de tester sa validité selon différents paramètres.

MéthodeCouverture de code

  • Une ligne de code est considérée comme couverte par un test si elle est exécutée durant le test.

  • Pour couvrir complètement le code, il faut tester la fonction avec différentes valeurs permettant de vérifier toutes les conditions.

  • Les conditions doivent être couvertes car elles sont souvent à l'origine de bugs.

Les frameworks de test permettent de calculer facilement la couverture de son code.

ExempleTester les valeurs retournées

Voici un exemple de test sur une fonction simple. Si le résultat attendu n'est pas identique à celui retourné par la fonction, le test échoue et affiche un message d'erreur.

1
/** JavaScript : teste unitairement la fonction backetPrice. */
2
function basketPrice (listItemPrices) {
3
  let res = 0
4
  for (let i = 0; i < listItemPrices.length; i++) {
5
    res = res + listItemPrices[i]
6
  }
7
  return res
8
}
9
10
function testBasketPrice () {
11
  if (basketPrice([2, 5, 29]) !== 36) {
12
    console.log('Test échoué totalPanier pour [2, 5, 29]')
13
    return false
14
  }
15
  if (basketPrice([]) !== 0) {
16
    console.log('Test échoué totalPanier pour []')
17
    return false
18
  }
19
  console.log('Test totalPanier réussi')
20
  return true
21
}
22
23
testBasketPrice()
24
1
"""Python : teste unitairement la fonction backet_price. """
2
def basket_price(list_item_prices):
3
  res = 0
4
  for i in range(len(list_item_prices)):
5
    res = res + list_item_prices[i]
6
  return res
7
8
def test_basket_price():
9
  if basket_price([2, 5, 29]) != 36:
10
    print("Test échoué basket_price pour [2, 5, 29]")
11
    return False
12
  if basket_price([]) != 0:
13
    print("Test échoué basket_price pour []")
14
    return False
15
  print("Test basket_price réussi")
16
  return True
17
18
19
test_basket_price()

Si lors d'une modification du code, une erreur est introduite, par exemple :

1
for (let i = 0; i < listItemPrices.length - 1; i++)

Le test échouera et affichera :

1
Test échoué totalPanier pour [2, 5, 29]

Ainsi, l'erreur est détectée et corrigeable avant que les utilisateurs n'expérimentent le bug directement.

ComplémentUtiliser la syntaxe assert

Pour simplifier les tests, il est possible d'utiliser un nouvel élément de syntaxe : l'assertion. Une assertion ne fera rien de particulier si l'expression qui la suit est vraie mais une erreur sera émise si l'expression est fausse (AssertionError pour être précis).

1
/** JavaScript : teste unitairement la fonction backetPrice en utilisant des assert. */
2
function basketPrice (listItemPrices) {
3
  let res = 0
4
  for (let i = 0; i < listItemPrices.length; i++) {
5
    res = res + listItemPrices[i]
6
  }
7
  return res
8
}
9
10
function testBasketPrice () {
11
  const assert = require('assert')
12
  assert(basketPrice([2, 5, 29]) === 36)
13
  assert(basketPrice([]) === 0)
14
  console.log('Test totalPanier réussi')
15
  return true
16
}
17
18
testBasketPrice()
19
1
"""Python : teste unitairement la fonction total_panier en utilisant des assert. """
2
def basket_price(list_item_prices):
3
  res = 0
4
  for i in range(len(list_item_prices)):
5
    res = res + list_item_prices[i]
6
  return res
7
8
def test_basket_price():
9
  assert basket_price([2, 5, 29]) == 36
10
  assert basket_price([]) == 0
11
  print("Test basket_price réussi")
12
  return True
13
14
15
test_basket_price()

Exemple

Voici un programme qui teste une fonction renvoyant la liste des prix TTC à partir des prix HT :

1
/** JavaScript : teste unitairement la fonction htToTtc. */
2
function htToTtc (htPriceList) {
3
  const ttcPriceList = []
4
  for (let i = 0; i < htPriceList.length; i++) {
5
    const ttcPrice = htPriceList[i] * 1.2
6
    ttcPriceList.push(ttcPrice)
7
  }
8
  return ttcPriceList
9
}
10
11
function testHtToTtc () {
12
  if (htToTtc([2, 5, 10]).toString() !== [2.4, 6, 12].toString()) {
13
    console.log('Test htToTtc échoue pour [2, 5, 10]')
14
    return false
15
  }
16
  if (htToTtc([]).toString() !== [].toString()) {
17
    // On teste également les valeurs extrêmes
18
    console.log('Test htToTtc échoue pour [2, 5, 10]')
19
    return false
20
  }
21
  console.log('Test réussi')
22
  return true
23
}
24
25
testHtToTtc()
26
1
"""Python : teste unitairement la fonction HT_to_TTC. """
2
def HT_to_TTC(ht_price_list):
3
  ttc_price_list = []
4
  for i in range(len(ht_price_list)):
5
    ttc_price = ht_price_list[i] * 1.2
6
    ttc_price_list.append(ttc_price)
7
  return ttc_price_list
8
9
def test_HT_to_TTC():
10
  if HT_to_TTC([2, 5, 10]) != [2.4, 6, 12]:
11
    print("Test HT_to_TTC échoue pour [2, 5, 10]")
12
    return False
13
  if HT_to_TTC([]) != []:  # On teste également les valeurs extrêmes
14
    print("Test HT_to_TTC échoue pour []")
15
    return False
16
  print("Test réussi")
17
  return True
18
19
test_HT_to_TTC()

ComplémentMocker des variables

Un développeur Web écrira des fonctions faisant des requêtes web ou vers des bases de données.

Dans le cadre d'un test, ces appels ne seront pas possibles car on ne peut se permettre d'interroger les systèmes en production. Pour pallier ce problème, il est possible d'utiliser des mocks qui remplacent certaines variables par une valeur pré-définie.

À retenir

  • Un test unitaire vérifie automatiquement le comportement d'une fonction, avec des valeurs simples et des valeurs extrêmes.

  • Un bon test unitaire doit prévoir tous les cas d'utilisation de la fonction testée.