unit-testing Tutoriel
Démarrer avec les tests unitaires
Recherche…
Remarques
Les tests unitaires décrivent le processus de test des unités de code individuelles indépendamment du système dont elles font partie. Ce qui constitue une unité peut varier d'un système à l'autre, allant d'une méthode individuelle à un groupe de classes étroitement liées ou d'un module.
L'unité est isolée de ses dépendances à l'aide de tests doubles si nécessaire et configurée dans un état connu. Son comportement en réaction aux stimuli (appels de méthode, événements, données simulées) est ensuite testé par rapport au comportement attendu.
Les tests unitaires de systèmes entiers peuvent être réalisés à l'aide de faisceaux de test écrits personnalisés. Cependant, de nombreux frameworks de test ont été écrits pour simplifier le processus et prendre en charge une grande partie des tâches de plomberie, répétitives et banales. Cela permet aux développeurs de se concentrer sur ce qu'ils veulent tester.
Lorsqu'un projet a suffisamment de tests unitaires, toute modification apportée à l'ajout de nouvelles fonctionnalités ou à la refactorisation de code peut être effectuée facilement en vérifiant à la fin que tout fonctionne comme avant.
La couverture du code , normalement exprimée en pourcentage, est la mesure type utilisée pour indiquer la part du code dans un système couverte par les tests unitaires; notez qu'il n'y a pas de règle stricte quant à la hauteur que cela devrait être, mais il est généralement admis que plus le niveau est élevé, mieux c'est.
Le développement piloté par les tests (TDD) est un principe qui spécifie qu'un développeur doit commencer à coder en écrivant un test unitaire défaillant et ensuite seulement pour écrire le code de production qui fait passer le test. En pratiquant le TDD, on peut dire que les tests eux-mêmes sont le premier consommateur du code en cours de création; Par conséquent, ils aident à auditer et à piloter la conception du code de manière à ce qu'il soit aussi simple à utiliser et aussi robuste que possible.
Versions
Le test unitaire est un concept qui n'a pas de numéro de version.
Un test élémentaire de base
Dans sa forme la plus simple, un test unitaire comprend trois étapes:
- Préparer l'environnement pour le test
- Exécutez le code à tester
- Valider le comportement attendu correspond au comportement observé
Ces trois étapes sont souvent appelées «Arrange-Act-Assert» ou «Given-When-Then».
Vous trouverez ci-dessous un exemple en C # qui utilise le framework NUnit .
[TestFixture]
public CalculatorTest
{
[Test]
public void Add_PassSevenAndThree_ExpectTen()
{
// Arrange - setup environment
var systemUnderTest = new Calculator();
// Act - Call system under test
var calculatedSum = systemUnderTest.Add(7, 3);
// Assert - Validate expected result
Assert.AreEqual(10, calculatedSum);
}
}
Si nécessaire, une quatrième étape de nettoyage facultative est prévue.
Un test unitaire avec une dépendance par écrasement
Les bons tests unitaires sont indépendants, mais le code a souvent des dépendances. Nous utilisons différents types de doubles de test pour supprimer les dépendances à tester. L'un des tests les plus simples est un stub. Ceci est une fonction avec une valeur de retour codée en dur appelée à la place de la dépendance du monde réel.
// Test that oneDayFromNow returns a value 24*60*60 seconds later than current time
let systemUnderTest = new FortuneTeller() // Arrange - setup environment
systemUnderTest.setNow(() => {return 10000}) // inject a stub which will
// return 10000 as the result
let actual = systemUnderTest.oneDayFromNow() // Act - Call system under test
assert.equals(actual, 10000 + 24 * 60 * 60) // Assert - Validate expected result
Dans le code de production, oneDayFromNow
appellerait Date.now (), mais cela provoquerait des tests incohérents et peu fiables. Donc, ici, nous le supprimons.
Un test unitaire avec un espion (test d'interaction)
Test de l'unité classique teste l' état , mais il peut être impossible de tester correctement les méthodes dont le comportement dépend d'autres classes via l'état. Nous testons ces méthodes à l'aide de tests d'interaction , qui vérifient que le système testé appelle correctement ses collaborateurs. Étant donné que les collaborateurs ont leurs propres tests unitaires, cela est suffisant et constitue en fait un meilleur test de la responsabilité réelle de la méthode testée. Nous ne testons pas que cette méthode retourne un résultat donné à une entrée, mais appelle plutôt ses collaborateurs.
// Test that squareOfDouble invokes square() with the doubled value
let systemUnderTest = new Calculator() // Arrange - setup environment
let square = spy()
systemUnderTest.setSquare(square) // inject a spy
let actual = systemUnderTest.squareOfDouble(3) // Act - Call system under test
assert(square.calledWith(6)) // Assert - Validate expected interaction
Test simple Java + JUnit
JUnit est la principale infrastructure de test utilisée pour tester le code Java.
La classe testée modélise un compte bancaire simple, qui facture une pénalité lorsque vous êtes à découvert.
public class BankAccount {
private int balance;
public BankAccount(int i){
balance = i;
}
public BankAccount(){
balance = 0;
}
public int getBalance(){
return balance;
}
public void deposit(int i){
balance += i;
}
public void withdraw(int i){
balance -= i;
if (balance < 0){
balance -= 10; // penalty if overdrawn
}
}
}
Cette classe de test valide le comportement de certaines méthodes publiques BankAccount
.
import org.junit.Test;
import static org.junit.Assert.*;
// Class that tests
public class BankAccountTest{
BankAccount acc;
@Before // This will run **before** EACH @Test
public void setUptestDepositUpdatesBalance(){
acc = new BankAccount(100);
}
@After // This Will run **after** EACH @Test
public void tearDown(){
// clean up code
}
@Test
public void testDeposit(){
// no need to instantiate a new BankAccount(), @Before does it for us
acc.deposit(100);
assertEquals(acc.getBalance(),200);
}
@Test
public void testWithdrawUpdatesBalance(){
acc.withdraw(30);
assertEquals(acc.getBalance(),70); // pass
}
@Test
public void testWithdrawAppliesPenaltyWhenOverdrawn(){
acc.withdraw(120);
assertEquals(acc.getBalance(),-30);
}
}
Test unitaire avec paramètres utilisant NUnit et C #
using NUnit.Framework;
namespace MyModuleTests
{
[TestFixture]
public class MyClassTests
{
[TestCase(1, "Hello", true)]
[TestCase(2, "bye", false)]
public void MyMethod_WhenCalledWithParameters_ReturnsExpected(int param1, string param2, bool expected)
{
//Arrange
var foo = new MyClass(param1);
//Act
var result = foo.MyMethod(param2);
//Assert
Assert.AreEqual(expected, result);
}
}
}
Un test élémentaire de base en python
import unittest
def addition(*args):
""" add two or more summands and return the sum """
if len(args) < 2:
raise ValueError, 'at least two summands are needed'
for ii in args:
if not isinstance(ii, (int, long, float, complex )):
raise TypeError
# use build in function to do the job
return sum(args)
Maintenant la partie test:
class Test_SystemUnderTest(unittest.TestCase):
def test_addition(self):
"""test addition function"""
# use only one summand - raise an error
with self.assertRaisesRegexp(ValueError, 'at least two summands'):
addition(1)
# use None - raise an error
with self.assertRaises(TypeError):
addition(1, None)
# use ints and floats
self.assertEqual(addition(1, 1.), 2)
# use complex numbers
self.assertEqual(addition(1, 1., 1+2j), 3+2j)
if __name__ == '__main__':
unittest.main()
Un test XUnit avec des paramètres
using Xunit;
public class SimpleCalculatorTests
{
[Theory]
[InlineData(0, 0, 0, true)]
[InlineData(1, 1, 2, true)]
[InlineData(1, 1, 3, false)]
public void Add_PassMultipleParameters_VerifyExpected(
int inputX, int inputY, int expected, bool isExpectedCorrect)
{
// Arrange
var sut = new SimpleCalculator();
// Act
var actual = sut.Add(inputX, inputY);
// Assert
if (isExpectedCorrect)
{
Assert.Equal(expected, actual);
}
else
{
Assert.NotEqual(expected, actual);
}
}
}
public class SimpleCalculator
{
public int Add(int x, int y)
{
return x + y;
}
}