J'ai donc un module d'authentification que j'ai écrit il y a quelque temps. Maintenant, je vois les erreurs de mon chemin et j'écris des tests unitaires pour cela. En écrivant des tests unitaires, j'ai du mal à trouver de bons noms et de bons domaines à tester. Par exemple, j'ai des choses comme
- RequirementsLogin_should_redirect_when_not_logged_in
- RequirementsLogin_should_pass_through_when_logged_in
- Login_should_work_when_given_proper_credentials
Personnellement, je pense que c'est un peu moche, même si ça semble "convenable". J'ai également du mal à faire la différence entre les tests en les balayant simplement (je dois lire le nom de la méthode au moins deux fois pour savoir ce qui vient d'échouer)
Donc, j'ai pensé que peut-être au lieu d'écrire des tests qui testent purement la fonctionnalité, peut-être écrire un ensemble de tests qui couvrent des scénarios.
Par exemple, voici un talon de test que j'ai trouvé:
public class Authentication_Bill
{
public void Bill_has_no_account()
{ //assert username "bill" not in UserStore
}
public void Bill_attempts_to_post_comment_but_is_redirected_to_login()
{ //Calls RequiredLogin and should redirect to login page
}
public void Bill_creates_account()
{ //pretend the login page doubled as registration and he made an account. Add the account here
}
public void Bill_logs_in_with_new_account()
{ //Login("bill", "password"). Assert not redirected to login page
}
public void Bill_can_now_post_comment()
{ //Calls RequiredLogin, but should not kill request or redirect to login page
}
}
Est-ce un motif entendu? J'ai vu des histoires d'acceptation et autres, mais c'est fondamentalement différent. La grande différence est que je propose des scénarios pour "forcer" les tests. Plutôt que d'essayer manuellement de trouver des interactions possibles que je devrai tester. De plus, je sais que cela encourage les tests unitaires qui ne testent pas exactement une méthode et une classe. Je pense que c'est OK cependant. De plus, je suis conscient que cela causera des problèmes pour au moins certains cadres de test, car ils supposent généralement que les tests sont indépendants les uns des autres et que l'ordre n'a pas d'importance (où il le serait dans ce cas).
Quoi qu'il en soit, est-ce un modèle conseillé du tout? Ou, serait-ce un ajustement parfait pour les tests d'intégration de mon API plutôt que comme des tests "unitaires"? C'est juste dans un projet personnel, donc je suis ouvert à des expériences qui peuvent ou non bien se passer.
_test
annexe et utilise des commentaires pour noter les résultats que j'attends. S'il s'agit d'un projet personnel, trouvez un style avec lequel vous vous sentez à l'aise et respectez-le.Réponses:
Oui, c'est une bonne idée de donner à vos tests les noms des exemples de scénarios que vous testez. Et utiliser votre outil de test unitaire pour plus que des tests unitaires peut-être bien aussi, beaucoup de gens le font avec succès (moi aussi).
Mais non, ce n'est certainement pas une bonne idée d'écrire vos tests d'une manière où l'ordre d'exécution des tests est important. Par exemple, NUnit permet à l'utilisateur de sélectionner de manière interactive le test qu'il souhaite exécuter, ce qui ne fonctionnera plus comme prévu.
Vous pouvez éviter cela facilement ici en séparant la partie de test principale de chaque test (y compris l '"assertion") des parties qui mettent votre système dans l'état initial correct. En utilisant votre exemple ci-dessus: écrivez des méthodes pour créer un compte, vous connecter et poster un commentaire - sans aucune assertion. Réutilisez ensuite ces méthodes dans différents tests. Vous devrez également ajouter du code à la
[Setup]
méthode de vos appareils de test pour vous assurer que le système est dans un état initial correctement défini (par exemple, aucun compte jusqu'à présent dans la base de données, personne connecté jusqu'à présent, etc.).EDIT: Bien sûr, cela semble être contraire à la nature "histoire" de vos tests, mais si vous donnez à vos méthodes d'assistance des noms significatifs, vous trouverez vos histoires dans chaque test.
Donc, cela devrait ressembler à ceci:
la source
Un problème avec le fait de raconter une histoire avec des tests unitaires est qu'il ne rend pas explicite que les tests unitaires doivent être organisés et exécutés de manière totalement indépendante les uns des autres.
Un bon test unitaire doit être complètement isolé de tout autre code dépendant, c'est la plus petite unité de code qui puisse être testée.
Cela donne l'avantage qu'en plus de confirmer que le code fonctionne, si un test échoue, vous obtenez gratuitement le diagnostic de l'endroit exact où le code est erroné. Si un test n'est pas isolé, vous devez regarder de quoi il dépend pour savoir exactement ce qui a mal tourné et passer à côté d'un avantage majeur du test unitaire. La question de l'ordre d'exécution peut également soulever beaucoup de faux négatifs, si un test échoue, il est possible que les tests suivants échouent malgré le code qu'ils testent parfaitement.
Un bon article plus en profondeur est le classique des tests hybrides sales .
Pour rendre les classes, les méthodes et les résultats lisibles, le grand test Art of Unit utilise la convention de dénomination
Classe de test:
Méthodes d'essai:
Pour copier l'exemple de @Doc Brown, plutôt que d'utiliser [Setup] qui s'exécute avant chaque test, j'écris des méthodes d'aide pour construire des objets isolés à tester.
Ainsi, les tests qui échouent ont un nom significatif qui vous donne un récit sur la méthode qui a échoué, la condition et le résultat attendu.
C'est comme ça que j'ai toujours écrit des tests unitaires, mais un ami a eu beaucoup de succès avec Gerkin .
la source
Ce que vous décrivez ressemble plus à une conception axée sur le comportement (BDD) qu'à des tests unitaires. Jetez un œil à SpecFlow, une technologie BDD .NET basée sur le DSL Gherkin .
Des trucs puissants que tout être humain peut lire / écrire sans rien savoir du codage. Notre équipe de test connaît un grand succès en tirant parti de nos suites de tests d'intégration.
Concernant les conventions pour les tests unitaires, la réponse de @ DocBrown semble solide.
la source
assert(value === expected)
BDD =value.should.equals(expected)
+ vous décrivez les entités en couches qui résolvent le problème de "l'indépendance des tests unitaires". C'est un super style!