Question

J'utilise actuellement une convention simple pour mes tests unitaires. Si j'ai une classe nommée "EmployeeReader", je crée une classe de test nommée "EmployeeReader.Tests. Je crée ensuite tous les tests de la classe dans la classe de test avec des noms tels que:

  • Reading_Valid_Employee_Data_Correctly_Generates_Employee_Object
  • Reading_Missing_Employee_Data_Throws_Invalid_Employee_ID_Exception

et ainsi de suite.

Je lisais récemment un article sur un type de convention de dénomination utilisé dans BDD. J'aime la lisibilité de cette dénomination, pour finir avec une liste de tests du genre:

  • When_Reading_Valid_Employee (fixture)
    • Employee_Object_Is_Generated (méthode)
    • Employee_Has_Correct_ID (méthode)
  • When_Reading_Missing_Employee (fixture)
    • An_Invalid_Employee_ID_Exception_Is_Thrown (méthode)

et ainsi de suite.

Quelqu'un at-il utilisé les deux styles de nommage? Pouvez-vous fournir des conseils, des avantages, des inconvénients, des pièges, etc. pour m'aider à décider si je souhaite changer de projet ou non?

Était-ce utile?

La solution

Votre deuxième exemple (avoir une fixture pour chaque "tâche" logique, plutôt qu'un pour chaque classe) présente l'avantage de pouvoir avoir différentes logiques SetUp et TearDown pour chaque tâche, simplifiant ainsi vos méthodes de test individuelles et les rendant plus lisible.

Il n'est pas nécessaire de choisir l'un ou l'autre comme standard. Nous utilisons une combinaison des deux, en fonction du nombre de "tâches" & différentes; nous devons tester pour chaque classe.

Autres conseils

La convention de dénomination que j'utilise est la suivante:

  

functionName_shouldDoThis_whenThisIsTheSituation

Par exemple, ce sont des noms de test pour la fonction "pop" d'une pile

  

pop_shouldThrowEmptyStackException_whenTheStackIsEmpty

     

pop_shouldReturnTheObjectOnTheTopOfTheStack_whenThereIsAnObjectOnTheStack

Je pense que le second est préférable car il rend vos tests unitaires plus lisibles pour les autres, car les longues lignes rendent le code plus difficile à lire ou à parcourir plus rapidement. Si vous estimez toujours qu'il y a une ambiguïté quant à ce que le test fait, vous pouvez ajouter des commentaires pour clarifier cela.

Une partie du raisonnement derrière la deuxième convention de dénomination à laquelle vous faites référence est que vous créez des tests et des spécifications comportementales en même temps. Vous établissez le contexte dans lequel les choses se passent et ce qui devrait réellement se passer dans ce contexte. (D'après mon expérience, les observations / méthodes de test commencent souvent par "devrait _", de sorte que vous obtenez un standard "When_the_invoicing_system_is_told_to_email_the_client" & "; devrait_initiate_connection_to_mail_server_server ""; format.)

Certains outils reflèteront vos montages de test et produiront une feuille de spécifications html bien formatée, supprimant les traits de soulignement. Vous vous retrouvez avec une documentation lisible par l'homme qui est synchronisée avec le code réel (tant que vous maintenez votre couverture de test élevée et précise).

En fonction du récit / de la fonctionnalité / du sous-système sur lequel vous travaillez, ces spécifications peuvent être montrées et comprises par les parties prenantes non programmées aux fins de vérification et de retour d'informations, ce qui est au cœur de l'agile et de BDD en particulier.

J'utilise une deuxième méthode, qui aide vraiment à décrire ce que votre logiciel doit faire. J'utilise également des classes imbriquées pour décrire un contexte plus détaillé.

En substance, les classes de test sont des contextes qui peuvent être imbriqués, et les méthodes sont toutes des assertions sur une ligne. Par exemple,

public class MyClassSpecification
{
    protected MyClass instance = new MyClass();

    public class When_foobar_is_42 : MyClassSpecification 
    {
        public When_foobar_is_42() {
            this.instance.SetFoobar( 42 ); 
        }

        public class GetAnswer : When_foobar_is_42
        {
            private Int32 result;

            public GetAnswer() {
                this.result = this.GetAnswer();
            }

            public void should_return_42() {
                Assert.AreEqual( 42, result );
            }
        }
    }
}

qui me donnera la sortie suivante dans mon coureur de test:

MyClassSpecification+When_foobar_is_42+GetAnswer
    should_return_42

J'ai parcouru les deux chemins que vous décrivez dans votre question ainsi que quelques autres ... Votre première alternative est assez simple et facile à comprendre pour la plupart des gens. Personnellement, j'aime davantage le style BDD (votre deuxième exemple), car il isole différents contextes et regroupe des observations sur ces contextes. Le seul inconvénient, c’est qu’il génère plus de code, ce qui le rend un peu plus lourd jusqu’à ce que vous voyiez les tests. De même, si vous utilisez l'héritage pour réutiliser la configuration de l'appareil, vous voulez un testrunner qui génère la chaîne d'héritage. Considérez une classe " An_empty_stack " et vous voulez le réutiliser afin de créer une autre classe: "When_five_is_pushed_on: An_empty_stack &"; vous voulez cela comme sortie et pas seulement "When_five_is_pushed_on". Si votre testrunner ne le supporte pas, vos tests contiendront des informations redondantes telles que: " When_five_is_pushed_on_empty_stack: An_empty_stack " juste pour rendre la sortie agréable.

Je vote en faveur de l'appel de la classe de cas de test EmployeeReaderTestCase et de l'appel de méthodes () telles que http: // xunitpatterns. com / Organization.html et http://xunitpatterns.com/Organization. html # Test% 20Naming% 20Conventions

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top