J'écris des tests pour un projet qui comprend plusieurs sous-modules. Chaque scénario 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 s'exécutent indépendamment, j'envisage d'appliquer un ordre d'exécution, car certains cas nécessitent plus d'un sous-module. 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 principale fonctionnalité que je teste est la connexion à un serveur distant de boîte noire, qui obtient uniquement les données du premier sous-module.
Dans ce cas, est-il acceptable 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 peux pas trouver de meilleur moyen de contourner.
edit: la question vient de Comment structurer les tests où un test est la configuration d'un autre test? car le test "précédent" n'est pas une configuration, mais teste le code qui effectue la configuration.
Je ne peux pas travailler avec des données factices, car la principale fonctionnalité que je teste est la connexion à un serveur distant de boîte noire, qui obtient uniquement les données du premier sous-module.
C'est l'élément clé pour moi. Vous pouvez parler de "tests unitaires" et de "s'exécuter indépendamment les uns des autres", mais ils semblent tous dépendre de ce serveur distant et du "premier sous-module". Donc, tout sonne étroitement couplé 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. Une exécution de test ordonnée, avec l'option d'un arrêt anticipé de l'exécution en cas de problème est parfaitement acceptable pour les tests d'intégration.
Mais, cela vaut également la peine de jeter un regard neuf sur la structure de votre application. Le fait de pouvoir simuler le premier sous-module et le serveur externe vous permettrait alors potentiellement d'écrire de vrais tests unitaires pour tous les autres sous-modules.
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 dépendez d'un service tiers.
Pour tester des éléments comme celui-ci, vous devez trouver un moyen d'injecter les données factices, par exemple en mettant en œuvre une interface de service de données qui reflète la demande Web mais renvoie des données connues à partir d'un fichier de données factices local.
L'ordre d'exécution forcé que vous proposez n'a de sens que si vous abandonnez également le test après le premier échec.
L'abandon du test sur le premier échec signifie que chaque test ne peut découvrir qu'un seul problème et qu'il ne peut pas trouver de nouveaux problèmes tant que tous les problèmes précédents n'ont pas été résolus. Si le premier test à exécuter détecte un problème qui prend un mois à résoudre, aucun test ne sera exécuté pendant ce mois.
Si vous n'interrompez pas l'exécution du test au premier échec, l'ordre d'exécution forcé ne vous rapporte rien car chaque test ayant échoué doit de toute façon être examiné. 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 qui a également été identifié sur le sous-module de génération de données.
Le meilleur conseil que je puisse donner est d'écrire les tests de manière à ce qu'il soit facile d'identifier quand un échec dans une dépendance entraîne l'échec du test.
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 les "tests automatisés" ou les "tests automatisés par un programmeur".
Les tests unitaires doivent être petits, indépendants et rapides.
Certaines personnes lisent incorrectement ceci comme "les tests automatisés écrits par un programmeur doivent être petits 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, et donc certaines des règles pour les tests unitaires ne devraient pas, ne peuvent pas ou ne doivent pas postulez pour vos tests. Un exemple trivial: vous devez exécuter vos tests unitaires après chaque build, 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, vous pouvez ignorer une règle et être autorisés à avoir 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 - quelque chose pour la portée d'une autre question .
Comme indiqué ci-dessus, ce que vous exécutez semble être un test d'intégration, mais vous déclarez 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 le refactoring. 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érateur de données). Au lieu de cela, il serait préférable d'injecter une interface contenant les méthodes pour accéder à ces données et cela peut ensuite être simulé 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 reproductibles pour des scénarios particuliers.
Les autres réponses mentionnent que le classement des tests est mauvais (ce qui est vrai la plupart du temps), mais il y a une bonne raison de faire respecter l'ordre lors de l'exécution des tests: s'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.