Est-ce une mauvaise pratique d'imposer un ordre d'exécution pour les tests unitaires?

84

J'écris des tests pour un projet composé de plusieurs sous-modules. Chaque cas de test que j'ai écrit s'exécute indépendamment les uns des autres et j'efface toutes les données entre les tests.

Même si les tests sont exécutés indépendamment, je songe à appliquer un ordre d'exécution, car certains cas requièrent plusieurs sous-modules. Par exemple, un sous-module génère des données et un autre exécute des requêtes sur les données. Si le sous-module générant les données contient une erreur, le test du sous-module de requête échouera également, même si le sous-module lui-même fonctionne correctement.

Je ne peux pas travailler avec des données factices, car la fonctionnalité principale que je teste est la connexion à un serveur distant Black Box, qui récupère uniquement les données du premier sous-module.

Dans ce cas, est-il correct d'appliquer un ordre d'exécution pour les tests ou est-ce une mauvaise pratique? J'ai l'impression qu'il y a une odeur dans cette configuration, mais je ne trouve pas de meilleur moyen de contourner le problème.

edit: la question est de Comment structurer des tests où un test est la configuration d'un autre test? comme le "précédent" test n'est pas une configuration, mais teste le code qui effectue la configuration.

Ali Rasim Kocal
la source
123
Si vous testez la connexion à un serveur distant, ils ne sont par définition pas des tests unitaires.
Telastyn
9
La première réponse m'a confondu ici parce que dans votre titre vous avez dit "Est-ce une mauvaise pratique?" et dans le résumé de votre question, vous écrivez "est-ce que ça va?" Si vous répondez par oui ou par non, vous allez en confondre un!
Liath
8
On dirait que vous créez un ensemble de tests d'intégration. Même pour cela, un test ne devrait pas s'appuyer sur d'autres tests.
Pélican volant à basse altitude
17
Si l'ordre compte, vous le faites probablement mal.
Mark Rogers

Réponses:

236

Je ne peux pas travailler avec des données factices, car la fonctionnalité principale que je teste est la connexion à un serveur distant Black Box, qui récupère uniquement les données du premier sous-module.

Ceci est la partie clé pour moi. Vous pouvez parler de "tests unitaires" et de "leur exécution indépendante", mais ils semblent tous s'appuyer sur ce serveur distant et sur le "premier sous-module". Donc, tout semble étroitement lié et dépend de l'état externe. En tant que tel, vous écrivez en fait des tests d'intégration. Il est tout à fait normal que ces tests soient exécutés dans un ordre spécifique car ils dépendent fortement de facteurs externes. Un test ordonné, avec l'option de quitter rapidement le test en cas de problème, est parfaitement acceptable pour les tests d'intégration.

Mais il serait également intéressant de jeter un regard neuf sur la structure de votre application. Le fait de pouvoir simuler le premier sous-module et le premier serveur externe vous permettrait potentiellement d’écrire de vrais tests unitaires pour tous les autres sous-modules.

David Arno
la source
14
Sans oublier que certains tests doivent vérifier spécifiquement que le comportement attendu se produit lorsque le serveur distant est indisponible.
Alexander
2
Ou peut-être avez-vous réellement l'intention d'écrire des tests d'intégration, et ainsi, se moquant des données, cela n'atteindra pas ce que vous essayez d'accomplir avec ces tests.
Guy Schalnat
10
Le problème est probablement que Junit a "unité" dans son nom.
Thorbjørn Ravn Andersen
7
@ ThorbjørnRavnAndersen Exactement. Les gens écrivent naturellement des tests d'intégration plutôt que des tests unitaires, parce que les tests d'intégration sont beaucoup plus utiles et beaucoup moins difficiles à écrire que les tests unitaires "réels". Mais parce que les frameworks de tests populaires ont été nommés d'après le concept de tests unitaires, le terme a été coopté et en vient à signifier "tout test automatisé" dans le langage moderne.
Mason Wheeler
1
@MasonWheeler Ou même coopté par des gestionnaires non techniques pour signifier tests d'acceptation.
TKK
32

Oui, c'est une mauvaise pratique.

Généralement, un test unitaire est destiné à tester une seule unité de code (par exemple une seule fonction basée sur un état connu).

Lorsque vous souhaitez tester une chaîne d'événements susceptibles de se produire dans la nature, vous souhaitez un style de test différent, tel qu'un test d'intégration. Cela est encore plus vrai si vous comptez sur un service tiers.

Pour effectuer des tests unitaires comme celui-ci, vous devez trouver un moyen d'injecter les données factices, par exemple en implémentant une interface de service de données reflétant la demande Web mais renvoyant des données connues à partir d'un fichier de données factice local.

Paul
la source
8
D'accord. Je pense que cette confusion provient du fait que beaucoup de gens ont l’idée que les tests d’intégration doivent être effectués de bout en bout et utilisent le "test unitaire" pour désigner tout test ne testant qu’une couche .
autophage
8
@autophage: Je suis tout à fait d'accord avec ça. En fait , je suis d' accord avec elle tant que je trouve régulièrement moi - même tomber dans le même piège , malgré admettant qu'il est un piège 😂
Légèreté Courses en orbite
16

L'ordre d'exécution forcée que vous proposez n'a de sens que si vous interrompez également l'exécution du test après le premier échec.

L'abandon du test lors du premier échec signifie que chaque test ne peut détecter qu'un seul problème et qu'il ne peut pas en détecter de nouveaux tant que tous les problèmes précédents n'ont pas été corrigés. Si le premier test à exécuter détecte un problème qui prend un mois à résoudre, aucun test ne sera exécuté au cours de ce mois.

Si vous n'abandonnez pas le test lors du premier échec, l'ordre d'exécution appliqué ne vous rapporte rien, car chaque test ayant échoué doit faire l'objet d'une enquête. Même si ce n'est que pour confirmer que le test sur le sous-module de requête échoue en raison de l'échec également identifié sur le sous-module générateur de données.

Le meilleur conseil que je puisse vous donner est d'écrire les tests de manière à pouvoir facilement identifier le moment où une défaillance dans une dépendance entraîne l'échec du test.

Bart van Ingen Schenau
la source
7

L'odeur à laquelle vous faites référence est l'application du mauvais ensemble de contraintes et de règles à vos tests.

Les tests unitaires sont souvent confondus avec "test automatisé" ou "test automatisé par un programmeur".

Les tests unitaires doivent être petits, indépendants et rapides.

Certaines personnes lisent à tort ceci comme "des tests automatisés écrits par un programmeur doivent être indépendants et rapides" . Mais cela signifie simplement que si vos tests ne sont pas petits, indépendants et rapides, ils ne sont pas des tests unitaires. Par conséquent, certaines des règles relatives aux tests unitaires ne doivent pas, ne peuvent pas ou ne doivent pas s'appliquer à vos tests. Un exemple trivial: vous devez exécuter vos tests unitaires après chaque construction, ce que vous ne devez pas faire pour les tests automatisés qui ne sont pas rapides.

Bien que vos tests ne soient pas des tests unitaires signifie que vous pouvez ignorer une règle et être autorisés à établir une certaine interdépendance entre les tests, vous avez également découvert qu'il existe d'autres règles que vous avez peut-être manquées et que vous devrez réintroduire - ce qui correspond à la portée d'une autre question. .

Peter
la source
6

Comme indiqué ci-dessus, ce que vous exécutez semble être un test d'intégration. Toutefois, vous indiquez que:

Par exemple, un sous-module génère des données et un autre exécute des requêtes sur les données. Si le sous-module générant les données contient une erreur, le test du sous-module de requête échouera également, même si le sous-module lui-même fonctionne correctement.

Et cela peut être un bon endroit pour commencer la refactorisation. Le module qui exécute des requêtes sur les données ne doit pas dépendre d'une implémentation concrète du premier module (génération de données). Au lieu de cela, il serait préférable d’injecter une interface contenant les méthodes permettant d’accéder à ces données. Celles-ci peuvent ensuite être simulées pour tester les requêtes.

par exemple

Si tu as:

class Queries {

    int GetTheNumber() {
        var dataModule = new Submodule1();
        var data = dataModule.GetData();
        return ... run some query on data
    }
}

Préférez plutôt:

interface DataModule {
    Data GetData();
}


class Queries {

    IDataModule _dataModule;

    ctor(IDataModule dataModule) {
       _dataModule = dataModule;
    }

    int GetTheNumber() {
        var data = _dataModule.GetData();
        return ... run some query on data
    }
}

Cela supprime la dépendance des requêtes sur votre source de données et vous permet de configurer des tests unitaires facilement répétables pour des scénarios particuliers.

Paddy
la source
6

Les autres réponses mentionnent que la commande de tests est mauvaise (ce qui est vrai la plupart du temps), mais il existe une bonne raison de faire respecter l’ordre lors de l’exécution de tests: vous assurer que vos tests lents (c’est-à-dire les tests d’intégration) s’exécutent après vos tests plus rapides (tests). qui ne dépendent pas d’autres ressources extérieures). Cela garantit que vous exécutez plus de tests plus rapidement, ce qui peut accélérer la boucle de rétroaction.

Mike Holler
la source
2
Je serais plus enclin à rechercher pourquoi un certain test unitaire se déroule lentement plutôt que d'exécuter un ordre. Les tests unitaires sont supposés être rapides.
Robbie Dee
L'OP dit qu'il ne peut pas utiliser de données factices pour certains de ces tests. Cela signifie une frappe de base de données, ce qui ralentit tous les tests (même certains tests unitaires véritables qui devraient fonctionner rapidement naturellement). S'il a d'autres tests qui ne nécessitent pas d'accès à la base de données, ils exécuteront un ordre de grandeur plus rapide que tout ce qui nécessite des accès au disque ou au réseau.
Mike Holler
2
Vous avez tous les deux raison, je pense; Robbie a raison de dire que les tests unitaires doivent être petits, rapides et isolés des dépendances; l'ordre ne devrait donc pas avoir d'importance et l'ordre aléatoire encourage souvent une meilleure conception en renforçant cette indépendance; Mike a raison de dire que commencer par des tests plus rapides est très, très bon pour les tests d’intégration . Comme dans les réponses ci-dessus, une partie du problème réside dans la terminologie des tests unitaires par rapport aux tests d'intégration.
WillC
@ MikeHoller Ensuite, ce ne sont pas des tests unitaires. Il ne devrait y avoir aucune confusion quant à la nature des tests unitaires .
Robbie Dee
@RobbieDee J'utilisais simplement la terminologie utilisée par l'OP. Je comprends que ce ne sont pas de vrais tests unitaires. Si vous voulez vous battre pour la terminologie, apportez-la avec OP. (D'où pourquoi j'ai clarifié avec "vrais tests unitaires" dans mon commentaire précédent ")
Mike Holler