590 likes | 774 Views
Génie Logiciel. Tom Mens Service de Génie Logiciel informatique.umons.ac.be/genlog. Les tests unitaires. Tester le code. Le but d’un test c’est de trouver des défauts dans le code Un test ne peut jamais “prouver” l’absence de défauts dans le code
E N D
Génie Logiciel Tom Mens Service de Génie Logiciel informatique.umons.ac.be/genlog
Tester le code • Le but d’un test c’est de trouver des défauts dans le code • Un test ne peut jamais “prouver” l’absence de défauts dans le code • Pour avoir un code source de bonne qualité, entre 25% et 50% du temps de développement doit être consacré au tests! • Des tests facilitent la maintenance et l’évolution • surtout quand on engage de nouveaux développeurs qui ne connaissent pas bien le système
Différentes manières detester le code • Utiliser des expressions de type System.out.println(…) pour imprimer de l’information sur le console lors de l’exécution du programme Avantage: • Très facile à deployer Problèmes: • Manque d’objectivité: les résultats doivent être interprétés par une personne • « scroll blindness »: il y a souvent trop de résultats affichés sur le console
Différentes manières detester le code • Utilisez des tests automatisés Inconvénients: • Exige la connaissance d’un test framework • p.e., JUnit • Ecrire les tests prend un certain temps Avantages • Objectivité: les tests sont exécutés et interprétés par l’ordinateur • Automatisation et indépendance: Les tests peuvent être exécutes à plusieurs reprises, indépendant du code source • Flexibilité: les tests peuvent être très complex
Sortes de test • Les tests unitaires (unit testing) • Testez le bon fonctionnement de chaque unité du logiciel • unité = un package, une classe, une méthode, ... • Les tests du système (system testing) • Testez que le système entier satisfait toutes les exigences fonctionnelles et non fonctionnelles • Les tests d'intégration (integration testing) • Testez le bon fonctionnement du système dans son environnement de déploiement • Les tests de réception (acceptance testing) • Test du logiciel ou d'un ensemble matériel-logiciel après installation, effectué par le client dans ses locaux avec la participation du développeur, afin de vérifier que les dispositions contractuelles ont été respectées.
Développement dirigé par les tests(test-driven development) • Idée générale • écrire les tests avant d’écrire le code source • automatiser l’exécution des tests • Cette idée est utilisé par les processus de développement agile • Par exemple, Extreme Programming • Références • « Test-driven development: Concepts, taxonomy and future directions » • David Janzen, Hossein Saiedian, IEEE Computer, September 2005, pp. 43-50
Développement dirigé par les tests(test-driven development) • Caractéristiques • Les tests doivent être le plus petit possible • des tests sont écrits pour des unités de programmes individuelles (p.e., les méthodes ou les classes) • on parle des tests unitaires (unit tests) • Les tests doivent être exécutés de manière automatique • tous les tests sont stockés dans un “test harness” ou “testing framework” (e.g., JUnit) • ceci simplifie l’écriture et l'exécution des tests • par conséquence, l’effort de testing est réduit • Les tests doivent être indépendants • des tests unitaires individuels ne dépendent pas d’autres tests unitaires • l’ordre d’exécution des tests n’est pas important • Le résultat d’un test doit être déterministe et ne devrait pas varier selon le contexte
Développement dirigé par les tests(test-driven development) • Caractéristiques • Tests are written before the actual source code is written • on parle de “test-first programming” • Test writing is an essential part of analysis and design • permet de préciser la fonctionnalité du programme avant de donner une implémentation détaillée • permet d’identifier des ambiguïtés ou des limitations dans la spécification • Tests can serve as active (i.e., executable) program documentation • Tests are never thrown away • après chaque changement du programme, on doit ré-exécuter les tests (cf. tests de regression) • si l’exécution de tous les tests prend trop de temps, exécutez un sous ensemble bien choisi et représentatif
Développement dirigé par les tests(test-driven development) • Micro processus de testing • Ce processus peut être utilisé comme une partie des autres processus de développement logiciel next iteration / add more functionality yes Refactor tests and program tests fail? tests succeed? tests succeed? Write tests yes Write program yes no no no “Test twice, code once”
Développement dirigé par les tests(test-driven development) • Souvent, des tests peuvent être extraits du cahier des charges (spécification du problème) • Quelques exemples de tests potentiels pour une application ATM • Le code PIN • Le code PIN doit toujours être composé de 4 chiffres • Si on change le code PIN, le nouveau code PIN doit être différent de l’ancien. • On ne peut pas faire une transaction si le code PIN est incorrect • Après 3 entrées incorrectes du code PIN, la carte sera avalée • La vérification d’un code PIN ne peut pas prendre plus que X millisecondes.
Développement dirigé par les tests(test-driven development) • Quelques exemples de tests potentiels pour une application ATM (suite) • Les virements • Le numéro de compte du titulaire doit correspondre à un compte à vue appartenant au titulaire • Le numéro de compte du bénéficiaire d’un virement doit différer du numéro de compte du titulaire • On ne peut pas utiliser comme numéro de compte du titulaire un compte d’épargne, sauf si le numéro de compte du bénéficiaire est le compte à vue du titulaire • On ne peut pas faire un virement si le solde est insuffisant • La “date mémo” (optionnelle) d’un virement doit être au moins 3 jours ouvrables après aujourd’hui • Une “communication structurée” d’un virement est composée de 3+4+5 chiffres, avec la condition que le numéro composé des 10 premiers chiffres modulo 97 = les 2 derniers chiffres
Développement dirigé par les tests(test-driven development) • Quelques exemples de tests potentiels pour une application ATM (suite) • Le compte • Un numéro de compte est composé de 3+7+2 chiffres • Les deux derniers chiffres = les 10 premiers modulo 97 • … • Les cartes • …. • Les autres types de transactions bancaires • …
Les tests unitaires • But: • “If tests are simple to create and execute, then programmers will be more inclined to create and execute tests.”
Les tests unitaires • Testez le bon fonctionnement de chaque unité du logiciel • les classes, les méthodes • Comment ça marche • mettez l'unité de test dans un certain état • exécutez le test • envoyez des messages et vérifiez la sortie • le test réussi si la sortie était prévue, autrement il échoue
Les tests unitaires • Les cadres de test (test framework) sont disponibles pour tous les langages principaux d'OO • Java: JUnit (www.junit.org) • Smalltalk: SUnit • C++: CppTest • .Net: NUnit (www.nunit.org) • Perl: PerlUnit • PhP: PUnit • Python: PyUnit • Référence • “Unit test frameworks: a language-independent overview” • Paul Hamill, O’Reilly, 2005. ISBN 0-596-00689-6
Les tests unitaires • Avantages • séparation des tests du code source • les tests n’encombrent pas le code et n'affectent pas sa performance ou sa taille • les tests peuvent être exécutes séparément du code • des tests unitaires peuvent être exécutés en isolation ou tous ensemble • Désavantages • couplage fort entre tests et code source • les changements du code exigent le changement des tests • certains aspects de la fonctionnalité logicielle sont difficiles à tester • GUI « look and feel » • l’interaction dans un système distribué
Les tests unitaires • Avantages • Avec des tests unitaires, on peut non seulement tester la fonctionnalité du code source • Tests « boite noire » (black box) • Mais également la structure du code source • Tests « boite blanche » (white box) • Et aussi certains caractéristiques non-fonctionnelles • Performance • Consommation de mémoire • …
Les tests unitaires • On peut même utiliser les tests unitaires pour l’activité de debugging • Quand un bug se produit, créez un test qui reproduit le bug • Fixez le bug • Ré-exécutez le test afin de s’assurer que le bug a disparu • Tests de regression • Après chaque changement, exécutez tous les « bug tests » afin d’éviter la réintroduction du bug
Les tests unitaires en Java avec JUnit 4 http://www.junit.org/
Les tests unitaires en Java JUnit • un cas de test (test case) • Une collection de tests pour une unité donnée (p.e., une classe) • Chaque test est spécifié par une méthode en Java, sans paramètre et sans type de retour • La méthode est annoté avec @Test • Le corps de la méthode contient un message assert • Exemple @Test public void testAddition() { float result= 2 + 3; assertTrue(result == 5); }
Les tests unitaires en Java JUnit Encore un exemple @Test public void createAndSetName() { Value v1 = new Value( ); v1.setName( "Y" ); String expected = "Y"; String actual = v1.getName( ); Assert.assertEquals( expected, actual ); }
Les tests unitaires en Java JUnit Encore un exemple @Test public void createAndSetName() { Value v1 = new Value( ); v1.setName( "Y" ); String expected = "Y"; String actual = v1.getName( ); Assert.assertEquals( expected, actual ); } Identifies this Java method as a test case, for the test runner
Les tests unitaires en Java JUnit Encore un exemple @Test public void createAndSetName() { Value v1 = new Value( ); v1.setName( "Y" ); String expected = "Y"; String actual = v1.getName( ); Assert.assertEquals( expected, actual ); } Objective: confirm that setName saves the specified name in the Value object v1
Les tests unitaires en Java JUnit Encore un exemple @Test public void createAndSetName() { Value v1 = new Value( ); v1.setName( "Y" ); String expected = "Y"; String actual = v1.getName( ); Assert.assertEquals( expected, actual ); } Check whether the Value object v1 really did store the name
Les tests unitaires en Java JUnit Encore un exemple @Test public void createAndSetName() { Value v1 = new Value( ); v1.setName( "Y" ); String expected = "Y"; String actual = v1.getName( ); Assert.assertEquals( expected, actual ); } expected and actual should be equal. If they aren’t, then the test case should fail.
Les tests unitaires en Java JUnit • Lors d’un test JUnit, 3 situations possibles Vert REUSSITE • La condition vérifiée est exécutable et vraie, p.e. publicvoid test1() { assertTrue(sqrt(4) == 2); } FAILURE • Condition vérifiée est exécutable mais fausse, p.e. publicvoid test2() { assertEquals(3, sqrt(4), 0.001); } ERREUR • Une expression exécutée lors du test à lancée une erreur ou exception, p.e. • Division par zéro publicvoid test3() { assertTrue(2 / 0 == 1); } Ça marche et ça fait ce qu’on veut. Ça ‘marche’ mais ça ne fait pas ce qu’on veut. Ça ne ‘marche’ même pas.
JUnit 4Les assertions • Assertions are static methods defined in the JUnit class Assert • They are used to determine the test case verdict, and check for various properties: • “equality” of objects • identical object references • null / non-null object references • They behave as follows • If an assertion is true, the method continues executing. • If any assertion is false, the method stops executing at that point, and the test case will fail. • If any other exception is thrown during the method, the result for the test case will be error. • If no assertions were violated for the entire method, the test case will pass.
JUnit 4Les assertions void fail() void assertTrue(boolean arg0) void assertFalse(boolean arg0) void assertNull(Object arg0) void assertNotNull(Object arg0) void assertSame(Object arg0, Object arg1) void assertNotSame(Object arg0, Object arg1) void assertEquals(expected,actual) void assertArrayEquals(expected,actual) void assertArraysAreSameLength (expected,actual,string) void assertThat(actual,matcher)
JUnit 4Les assertions • The unconditional failure assertion fail()always results in a fail verdict. • Boolean conditions are true or false assertTrue(condition) assertFalse(condition) • Objects are null or non-null assertNull(object) assertNotNull(object) • Objects are identical (i.e. two references to the same object), or not identical. assertSame(expected, actual) • true if: expected == actual assertNotSame(expected, actual)
JUnit 4Les assertions • “Equality” of objects: assertEquals(expected, actual) • valid if: expected.equals( actual ) • “Equality” of arrays: assertArrayEquals(expected, actual) • arrays must have same length • for each valid value for i, check as appropriate: assertEquals(expected[i],actual[i]) or assertArrayEquals(expected[i],actual[i])
JUnit 4Les assertions Assertion method parameters • In any assertion method with two parameters, the first parameter is the expected value, and the second parameter should be the actual value. • This does not affect the comparison, but this ordering is assumed for creating the failure message to the user. • Any assertion method can have an additional String parameter as the first parameter. The string will be included in the failure message if the assertion fails. • Examples: fail( message ) assertEquals( message, expected, actual)
JUnit 4Les assertions Equality assertions • assertEquals(a,b) relies on the equals() method of the class under test. • The effect is to evaluate a.equals( b ). • It is up to the class under test to determine a suitable equality relation. JUnit uses whatever is available. • Any class under test that does not override the equals() method from class Object will get the default equals() behaviour – that is, object identity. • If a and b are of a primitive type such as int, boolean, etc., then the following is done for assertEquals(a,b) : • a and b are converted to their equivalent object type (Integer, Boolean, etc.), and then a.equals( b ) is evaluated.
JUnit 4Les assertions Floating point assertions • When comparing floating point types (double or float), there is an additional required parameter delta. • The assertion evaluates Math.abs( expected – actual ) <= delta to avoid problems with round-off errors with floating point comparisons. • Example: assertEquals( aDouble, anotherDouble, 0.0001 )
JUnit 4 • JUnit 4 est une refonte majeure du framework JUnit • exige au moins Java 5 • Utilise les annotations @BeforeClass • Pour initialiser la classe de test p.e. pour connecter à une base de données @Before • Pour initialiser chaque méthode de test @Test • Pour les méthodes de test @After • Pour déinitialiser chaque méthode de test @AfterClass • Pour déinitialiser la classe de test p.e. pour déconnecter de la base de données @Ignore • Pour ne pas exécuter un certain test p.e. parce qu’il prend trop de temps
JUnit 4 • Avec JUnit 4 on peut tester plus facilement • Les boucles infinies, en spécifiant une limite de temps (en millisecondes) @Test (timeout=10) public void greatBig() { assertTrue(program.ackerman(5, 5) > 10e12);} • Les exceptions lancées par une méthode @Test (expected=IllegalArgumentException.class)public void factorial() { program.factorial(-5);}
Utilisation d’un “test fixture” Un “test fixture” permet de définir et d’initialiser des ressources necéssaires lors des tests, et de les détruire après les tests • Le code suivant @Before est exécuté avant chaque méthode de test • Le code suivant @After est exécuté après chaque méthode de test • Le code suivant @BeforeClass est exécuté une fois, avant la première méthode de test. C’est comme un constructeur • Le code suivant @AfterClass est exécuté une fois, après la dernière méthode de test. C’est comme un destructeur (en C ou C++) import org.junit.*; import static org.junit.Assert.*; public class MoneyTest { private Money f12CHF; private Money f14CHF; @Before public void setUp() { f12CHF= new Money(12, "CHF"); f14CHF= new Money(14, "CHF"); } @Test public void testSubtract() { assertEquals(new Money(2, "CHF"), f14CHF.subtract(f12CHF)); } @Test public void testNegate() { assertEquals(new Money(-14, "CHF"), f14CHF.negate()); }
Utilisation d’un “test fixture” • Remarque: • Si une classe hérite d’une superclasse, elle hérite également les méthodes @Before and @After • L’exécution sera la suivante, pour chaque méthode @Test: • Exécutez les méthodes @Before de la superclasse • Exécutez les méthodes @Before de la classe • Exécutez la méthode @Test de la classe • Exécutez les méthodes @After de la classe • Exécutez les méthodes @After de la superclasse
Exemple • NumberCruncher.java, contient des méthodes de calcul arithmétique simple • public Boolean isPrime(Integer n) • Renvoie true si n est en nombre premier • public Integer factorial(Integer n) • Renvoie la factorielle de la valeur n • Par exemple, factorial(6) = 1 x 2 x 3 x 4 x 5 x 6 = 720. • public Array<Integer> getFactors(Integer n) • Renvoie un ArrayList de valeurs de type Integer qui représentant la factorisation de n. • Par exemple getFactors(12) = {1, 2, 3, 4, 6, 12} • public Integer doForever() • Devrait renvoyer une valeur mais représente une boucle infinie…
ExempleNumberCruncher.java package numbers; import java.util.ArrayList; public class NumberCruncher { public boolean isPrime(Integer n) { boolean value = true; for (int i = 2; i < n.intValue(); i++) { if (n.intValue() % i == 0) { value = false; break; } } return value; } public Integer factorial(Integer n) throws IllegalArgumentException { if (n.intValue() < 1) { throw new IllegalArgumentException("Factorial cannot be computed."); } int value = 1; for (int i = 2; i <= n.intValue(); i++) { value = value * i; } return new Integer(value); } public Object[] getFactors(Integer n) { ArrayList factors = new ArrayList(); for (int i = 1; i <= n.intValue(); i++) { if (n.intValue() % i == 0) { factors.add(new Integer(i)); } } return factors.toArray(); } public Integer doForever() { // code with infinite loop Integer v= new Integer(0); boolean finished = false; while (!finished) { v= new Integer(v.intValue()+1); } return v; } }
ExempleNumberCruncherTest.java package junit; import numbers.NumberCruncher; import org.junit.*; import static org.junit.Assert.*; public class NumberCruncherTest { @Test public void testFactorial() { NumberCruncher nc = new NumberCruncher(); assertEquals( new Integer(120), nc.factorial(new Integer(5))); } // testFactorial() @Test public void testIsPrime() { NumberCruncher nc = new NumberCruncher(); assertTrue("17 is prime", nc.isPrime(new Integer(17))); assertFalse("16 is not prime", nc.isPrime(new Integer(16))); } // testIsPrime() @Ignore ("Not ready yet") public void testSomethingElse() { NumberCruncher nc = new NumberCruncher(); }
ExempleNumberCruncherTest.java @Test (expected = IllegalArgumentException.class) public void testFactorialInvalid() { NumberCruncher nc = new NumberCruncher(); nc.factorial(new Integer(-4)); } @Test (timeout = 5000) public void testDoForever() { NumberCruncher nc = new NumberCruncher(); assertEquals(new Integer(0), nc.doForever()); } @Test public void testGetFactors() { NumberCruncher nc = new NumberCruncher(); ArrayList<Integer> f1 = nc.getFactors(new Integer(32)); ArrayList<Integer> f2 = nc.getFactors(new Integer(32)); assertSame(f1, f2); } }
Exécuter une suite de tests • Une suite de tests est un ensemble de plusieurs classes de test package junit.samples; import org.junit.runner.RunWith; import org.junit.runners.Suite; @RunWith(Suite.class) @Suite.SuiteClasses({ NumberTester.class, SimpleTest.class, ListTest.class, MoneyTest.class}) public class AllTests { }
Exécuter JUnit • The JUnit 4 framework does not provide a graphical test runner. Instead, it provides • a textual runner than can be used from a command line • an API that can be used by IDEs to run test cases • Eclipse and Netbeans each provide a graphical test runner that is integrated into their respective environments.
Exécuter Junit Eclipse plug-in • Run as > • JUnit test • Vue hiérarchique sur les tests • Suite • Classe • Méthode • intégré d'une manière transparente dans l’IDE • Supporte le traçage des échecs
Exécuter JUnit • With the test runner provided by JUnit: • When a class is selected for execution, all the test case methods in the class will be run. • The order in which the methods in the class are called (i.e. the order of test case execution) is notpredictable. • Test runners provided by IDEs may allow the user to select particular methods, or to set the order of execution. • It is good practice to write tests with are independent of execution order, and that are without dependencies on the state any previous test(s).
Exécuter JUnit de la ligne de commande • Si on veut exécuter la classe testClass à partir de la ligne de commande java -cp classpath -ea org.junit.runner.JUnitCore testClass -eaest utilisé pour “enable assertions” • On peut aussi rendre une classe de test auto-exécutable. public class AllTests { public static void main(String argsp[]) { org.junit.runner.JUnitCore.main(AllTests.class.getName()); }}
Exécuter JUnit de la ligne de commande JUnit version 4.6 ....E.E.E..E.E........................... Time: 5.079 There were 5 failures: 1) testDoForever(junit.samples.NumberTester) java.lang.Exception: test timed out after 5000 milliseconds at … 2) testGetFactors(junit.samples.NumberTester) java.lang.AssertionError: expected same:<[Ljava.lang.Object;@a7508a> was not:<[Ljava.lang.Object;@98cb3d> at … 3) testAdd(junit.samples.SimpleTest) java.lang.AssertionError: at … 4) testDivideByZero(junit.samples.SimpleTest) java.lang.ArithmeticException: / by zero at … 5) testEquals(junit.samples.SimpleTest) java.lang.AssertionError: Size expected:<12> but was:<13> at … FAILURES!!! Tests run: 36, Failures: 5
Les tests unitaires en JavaTester les exceptions import java.util.ArrayList; import org.junit.*; import static org.junit.Assert.*; public class ExceptionTest { @Test(expected = IndexOutOfBoundsException.class) public void indexOutOfBounds() { new ArrayList<Object>().get(0); } @Test(expected = java.lang.ArithmeticException.class) public void divisionByZero() { int result = 1 / 0; } public void myOwnExceptionMethod() throws MyOwnException { throw(new MyOwnException()); } @Test(expected = MyOwnException.class) public void myOwnExceptionTest() throws MyOwnException { myOwnExceptionMethod();} }
Développement dirigé par les testsavec JUnit • Créez la classe C que tu veux implémenter • Ajoutez la signature des méthodes de C que tu veux implémenter • Créez la classe de test TC • Si necéssaire, spécifiez un “test fixture” • Pour chaque méthode m de C • Ajoutez une ou plusieurs méthodes de test dans TC • Vérifiez que les méthodes de test échouent • Ajoutez le corps de la méthode m • Vérifiez que les méthodes de test réussissent