Property-based testing SVL - Cours-TD9
Mirabelle Nebut
Bureau 203 - M3 ext mirabelle.nebut at univ-lille.fr
Master 1 info S2 - 2016.2017
But de ce cours
Faire un retour sur l’´ecriture des tests
`a la lumi`ere de la programmation par contrats et de l’analyse statique de programmes.
Avec des contrats. . .
On peutprogrammer en supervisant ou non automatiquement les contrats (cf C#, JML).
On peut (tenter de)v´erifier que leprogrammeest conforme `a ses contrats(cf Spec#, openJML).
Mais quid du TDD ?
⇒Derri`ere chaque oraclese cache uncontrat implicite.
Mais pas tous les contrats, malheureusement. . . essentiellement lequel ?
Int´egrer l’esprit des contrats dans les tests Post-conditions
Pre-conditions Invariants
Property-based testing
Property-based testing avec Hypothesis Quelles propri´et´es utiliser ?
Lien avec les parameterized tests et theories de JUnit
Avec un test type TDD de base
public void testPaulDebiteSonCompte() { Compte compte = new Compte();
compte.crediter(50);
compte.debiter(12);
assertEquals(38, compte.solde()) }
Tel que c’est ´ecrit :
I le test pr´esente un exemple
I le contrat sous-jacent `a l’oracle ne saute pas aux yeux.
⇒quoi modifier en premi`ere approche ?
Avec un test type TDD, en mieux
public void
testPaulDebiteSonCompteLeSoldeEstDiminueDuMontant() { Compte compte = new Compte();
compte.crediter(50);
compte.debiter(12);
assertEquals(38, compte.solde()) }
Le nom du test doit faire apparaˆıtre la propri´et´e test´ee.
Chaque test doit ˆetre clairement reli´e :
I `a une fonctionnalit´e, unbesoin du client (cf nom de la classe de test)
I une propri´et´e du programme.
De l’oracle ` a la propri´ et´ e
La propri´et´e sous-jacente est :
//@ ensures solde = \old(solde) - montant;
public void debiter(float montant) { ... }
Mˆeme sans utiliser un langage de sp´ecification ou des contrats supervis´es, cettepost-condition existe.
Vous devez l’avoir en tˆete en ´ecrivant les tests.
Avec un oracle ` a la post-condition
public void
testPaulDebiteSonCompteLeSoldeEstDiminueDuMontant() { Compte compte = new Compte();
compte.crediter(50);
ancien_solde = compte.solde();
montant = 12;
compte.debiter(montant);
assertEquals(ancien_solde - montant, compte.solde()) }
I l’oracle est plus clair, mais pas le reste du test ;
I Il est souvent conseill´e dans les bonnes pratiques d’utiliser une constanteen durdans l’oracle.
I on peut r´ep´eter ce cas de test avec d’autres DT (ex : valeur limite).
Int´egrer l’esprit des contrats dans les tests Post-conditions
Pre-conditions Invariants
Property-based testing
Property-based testing avec Hypothesis Quelles propri´et´es utiliser ?
Lien avec les parameterized tests et theories de JUnit
2 cas selon le traitement des pr´ e-conditions
A la programmation d´` efensive:
I = lev´ee d’exception
I post-condition exceptionnelle
I mˆeme cons´equence sur l’oracle qu’une post-condition normale A la contrat` = pr´e-condition (supervis´ee ou non) :
I g´en´eralement on teste toujours `a l’int´erieur du domaine de la m´ethode
I ⇒ les pr´e-cond apparaissent implicitement dans le setUp, mais pas en tant qu’oracle
I dans le cas de l’utilisation syst´ematique de pr´e-conditions supervis´ees, il peut ˆetre utile de tester qu’on ne s’est pas tromp´e dans l’´ecriture de la spec.
Avec des post-conditions exceptionnelles
Le contrat implicite est :
//@ signals (MontantError) montant <= 0;
public void debiter(float montant) { ... } On peut ´ecrire le test ainsi :
public void
testPaulDebiteSonCompteAvecUnMontantNegatifEchec() { ...
montant_negatif = -2
with self.assertRaises(MontantError):
compte.debiter(montant_negatif);
}
Int´egrer l’esprit des contrats dans les tests Post-conditions
Pre-conditions Invariants
Property-based testing
Property-based testing avec Hypothesis Quelles propri´et´es utiliser ?
Lien avec les parameterized tests et theories de JUnit
Quels invariants ?
Les invariants, je ne vois pas `a quoi ¸ca sert un ´etudiant qui se reconnaˆıtra.
Un invariant d´ecrit une propri´et´e toujours vraiede toute instance d’une classe :
I le solde est toujours positif
I la tour est toujours tri´ee par taille de disque d´ecroissante
I un arbre est toujours ´equilibr´e
I l’indice de tableau est toujours compris dans ses bornes L’invariant porte surl’´etatde l’objet.
Une violation de l’invariant indique que l’objet de trouve dans un
´etat inconsistant.
Quels invariants ?
En programmation par contrats c’est une notion fondamentale :
I l’invariant est v´erifi´e avant et apr`es chaque appel de m´ethode.
I on peut le voir comme une post-condition implicite, mais c’est plus que ¸ca avec Spec#.
En v´erification c’est aussi une propri´et´e fondamentale `a prouver.
Mais chez les testeurs. . . souventon l’oublie.
Pas facile `a faire apparaˆıtre dans les tests.
Invariants et test
Rajoute-t-on un oracle codant l’invariant `a la fin de chaque test ? Ou dans une m´ethode detearDown?
Ou unassertdans le code ?
On n’atteindra pas la lisibilit´e d’un invariant sp´ecifi´e.
Qui plus est on rate letoujours vrai de toute instance.
Tests, documentation et sp´ ecification
Les tests jouent le rˆole de documentation / sp´ecification ex´ecutable par l’exemple.
On dit parfois que les tests sp´ecifient un contrat ou une propri´et´e, mais le terme tel quel me semble impropre.
Les tests n’ont pas vocation `a remplacer les sp´ecifications.
Les approches TDD / sp´ecification formelle des contrats sont compl´ementaires.
´Ecrire les contrats dans le code (`a titre informatif) s’int`egre bien avec le TDD, et indique quoi tester.
Limitation intrins` eque au test
On teste toujours pour des DT donn´ees, donc pour des cas particuliers.
Vs le contrat qui est implicitement quantifi´e universellement :
I la post-condition est vraie qqsoit les valeurs de ses param`etres
I l’invariant est vrai qqsoit les appels re¸cus par les instances Vs la v´erification effectu´ee par les analyses statiques.
Repousser les limites avec les contrats + g´ en´ erateurs de test
On peut g´en´erer automatiquement des donn´ees de test et des instances. . .
et v´erifier post-conditions et invariants pour les donn´ees qui satisfont les pr´e-conditions.
Cf AutoTest pour Eiffel, les outils pour JML.
Avantage : la g´en´eration al´eatoire d’un grand nombre de DT nous rapproche de la quantification universelle.
Inconv´enient : ne s’inscrit pas bien dans un d´eveloppement `a la TDD.
Repousser les limites avec le property-based testing
Principe :
I utiliser comme oracle une propri´et´e toujours vraie
I g´en´ererautomatiquement etal´eatoirementun grand nombre deDT
I voire g´en´erer aussi al´eatoirement des sc´enarios de test
Int´egrer l’esprit des contrats dans les tests Post-conditions
Pre-conditions Invariants
Property-based testing
Property-based testing avec Hypothesis Quelles propri´et´es utiliser ?
Lien avec les parameterized tests et theories de JUnit
Historique
Biblioth`eque QuickCkeck d’Haskell (1999).
Portage pour divers langages :
I Hypothesis pour Python (2015 ?)
I junit-quickcheck pour JUnit (d´ebuts en 2010 via les th´eories) Les principes sont tr`es bien expliqu´es dans cet expos´e de Scott Wlaschin :
http://fsharpforfunandprofit.com/pbt/
4 principes
1. g´en´erer al´eatoirement les donn´ees de testvues comme des param`etres pour les tests
2. utiliser un oracletout le temps vrai
3. shrinking : en cas d’´echec du test, trouver une DT minimale qui fait aussi ´echouer le test et est plus facile `a comprendre 4. g´en´erer al´eatoirement des sc´enarios de test
Int´egrer l’esprit des contrats dans les tests Post-conditions
Pre-conditions Invariants
Property-based testing
Property-based testing avec Hypothesis Quelles propri´et´es utiliser ?
Lien avec les parameterized tests et theories de JUnit
Principes
Biblioth`eque tr`es riche.
J’ai rencontr´e des pbs avec le shrinking pour la strat´egiefloats:
I tr`es grandes valeurs retourn´ees `a l’utilisateur
I ex : 2.882303761517118e+17
I du coup erreurs dans les calculs et contrats qui ´echouent.
⇒g´en´erer des entiers.
G´ en´ eration al´ eatoires des donn´ ees de test
Letest est param´etr´e par les valeurs `a g´en´erer al´eatoirement.
Le d´ecorateurgiven pr´ecise la strat´egie`a utiliser lors de la g´en´eration.
from hypothesis import given
from hypothesis.strategies import text, floats
@given(text())
def test_decode_inverts_encode(s):
...
@given(somme=floats(min_value=0.0))#, allow_nan=False))
# par d´efaut si min ou max utilis´e
def test_le_solde_est_augmente_sans_filter(self, somme):
...
D´ efinition de strat´ egies
from hypothesis.strategies import floats strat_float_strict_positif =
floats(min_value=0.0, allow_infinity=False) .filter(lambda s: s > 0)
strat_float_strict_negatif =
floats(max_value=0.0, allow_infinity=False) .filter(lambda s: s < 0)
strat_float_negatif_ou_nul = floats(max_value=0.0)
@given(somme=strat_float_strict_positif)
def test_le_solde_est_augmente(self, somme):
...
D´ efinition de strat´ egies compliqu´ ees
D´ecorateur compositepour composer arbitraitement des strat´egies.
from collections import namedtuple from hypothesis import composite SommeDecouvertSolde =
namedtuple(’SommeDecouvertSolde’,
[’somme’, ’decouvert’, ’solde_initial’])
@composite
def strat_solde_somme_decouvert(draw):
somme = draw(strat_float_strict_positif) decouvert = draw(strat_float_strict_negatif) solde_initial =
draw(floats(min_value = somme + decouvert) .filter(lambda x : x > 0))
return SommeDecouvertSolde(somme=somme,
decouvert=decouvert,
Combien d’exemples s´ electionne Hypothesis ?
Par d´efaut 200, se change par @settings.
Hypothesis tente une valeur en utilisant la strat´egie, et ressaie si elle ne passe pas lefilter.
Se voit parpytest --hypothesis-show-statistics test ...
Attention `a ne pas trop contraindre : si pas ou trop peu de donn´ees s´electionn´ees on s’´eloigne de l’objectif !
Compte-rendu g´ en´ er´ e par Hypothesis
test_even_integers:
- 200 passing examples, 0 failing examples, 16 invalid examples - Typical runtimes: < 1ms
- Stopped because settings.max_examples=200 - Events:
* 30.56%, Retried draw from integers().filter(lambda x: x % 2 == 0) to satisfy filter
* 7.41%, Aborted test because unable to satisfy integers().filter(lambda x: x % 2 == 0)
Assume
Pour supprimer des valeurs non d´esir´ees g´en´er´ees par une strat´egie.
assume expr`a mettre en tˆete de test.
G´en`ere une exception r´ecup´er´ee par Hypothesis.
A utiliser avec pr´` ecaution : Hypothesis essaie de trouver des donn´ees qui passent leassumemais n’en trouve pas toujours.
Privil´egier lesfilter.
G´ en´ eration al´ eatoires de sc´ enarios de test
Oustateful testing.
Implant´e par desRule based state machines.
Principe :
I peupler une collection d’objets (Bundle) avec des valeurs g´en´er´ees comme pr´ec´edemment ;
I appliquer al´eatoire aux objets du Bundle une suite al´eatoire d’appels `a des m´ethodes dont les param`etres sont g´en´er´es comme pr´ec´edemment.
I pr´evoir une m´ethode qui inclut une propri´et´e
Ainsi la propri´et´e sera v´erifi´ee sur un objet issu d’un setUp al´eatoire.
Exemple de la doc Hypothesis
Exemple des arbres ´equilibr´es (mais qui ne le sont pas).
Rajouter la syntaxe ici.
Int´egrer l’esprit des contrats dans les tests Post-conditions
Pre-conditions Invariants
Property-based testing
Property-based testing avec Hypothesis Quelles propri´et´es utiliser ?
Lien avec les parameterized tests et theories de JUnit
Le property-based testing a l’air cool !
Mais ce n’est pas simple de trouver les propri´et´es adapt´ees ! Propri´et´es type pr´econis´ees par la communaut´e fonctionnelle (cf expos´e Scott Wlaschin).
Apr`es, il faut pratiquer pour s’entraˆıner.
Propri´ et´ es pr´ econis´ ees
Different paths, same destination (commutativit´e) : add 1puis add 2idem que add 2puis add 1
There and back again: addition/subtraction, write/read,
setProperty/getProperty, ou encore insert/contains, create/exists Some things never change : lesinvariants!
The more things change, the more they stay the same : idempotence
propri´et´es inductives pour structure de donn´ees r´ecursives
Hard to prove, easy to verify : ex si on concat`ene les tokens ´emis par un lexeur, on retrouve l’entr´ee
Questions sur le PBT objet
I Quelle int´egration avec le TDD ?
I Compl´ementaire du TDD ?
I Peut-on exprimer tout oracle en PBT de mani`ere simple ? Ou n’est-ce pas fait pour ¸ca ?
I Peut-on utiliser le PBT pour tester un invariant en g´en´erant al´eatoirement les sc´enarios de test ? par ex quand Spec# nous dit que l’invariant n’est pas v´erifi´e et qu’on ne comprend pas pourquoi ?
Exemple de test d’invariant
Reprise de l’ex du DS de test.
Int´egrer l’esprit des contrats dans les tests Post-conditions
Pre-conditions Invariants
Property-based testing
Property-based testing avec Hypothesis Quelles propri´et´es utiliser ?
Lien avec les parameterized tests et theories de JUnit
Parameterised test
I le test est appel´e de mani`ere r´ep´et´ee sur une collection de DT
I mais les DT sont fournies par l’utilisateur.
I pas de g´en´eration de DT
I syntaxe lourdingue
Theories
https://github.com/junit-team/junit4/wiki/Theories Mˆeme principe que le parameterized tests mais :
I la syntaxe est plus heureuse (@DataPoint)
I le assumeThatpermet de filtrer des DT
I possibilit´e d’´ecrire son propre fournisseur de valeurs