J'ai besoin de jouer un peu l'avocat du diable sur cette question car je ne peux pas bien la défendre par manque d'expérience. Voici l'affaire, j'obtiens conceptuellement les différences entre les tests unitaires et les tests d'intégration. En se concentrant spécifiquement sur les méthodes de persistance et le référentiel, un test unitaire utiliserait une maquette éventuellement via un framework comme Moq pour affirmer que par exemple une commande recherchée a été retournée comme prévu.
Disons que j'ai construit le test unitaire suivant:
[TestMethod]
public void GetOrderByIDTest()
{
//Uses Moq for dependency for getting order to make sure
//ID I set up in 'Arrange' is same one returned to test in 'Assertion'
}
Donc, si je configure OrderIdExpected = 5
et mon faux objet renvoie 5
comme ID, mon test réussira. J'ai compris. J'ai testé unité le code pour m'assurer que ce que mon code préforme renvoie l'objet et l'ID attendus et pas autre chose.
L'argument que j'obtiendrai est le suivant:
"Pourquoi ne pas simplement sauter les tests unitaires et faire des tests d'intégration? C'est tester la procédure stockée de la base de données et votre code ensemble qui est important. Il semble trop de travail supplémentaire d'avoir des tests unitaires et des tests d'intégration quand finalement je Je veux savoir si les appels de la base de données et le code fonctionnent. Je sais que les tests prennent plus de temps, mais ils doivent être exécutés et testés, donc il me semble inutile d'avoir les deux. Il suffit de tester par rapport à ce qui compte. "
Je pourrais le défendre avec une définition de livre de texte comme: "Eh bien, c'est un test d'intégration et nous devons tester le code séparément comme un test unitaire et, yada , yada, yada ... "C'est un cas où une explication puriste des pratiques contre la réalité se perd. Je rencontre cela parfois et si je ne peux pas défendre le raisonnement derrière le code de test unitaire qui repose finalement sur des dépendances externes, alors je ne peux pas le justifier.
Toute aide sur cette question est grandement appréciée, merci!
Les tests unitaires et les tests d'intégration ont des objectifs différents.
Les tests unitaires vérifient la fonctionnalité de votre code ... que vous obtenez ce que vous attendez de la méthode lorsque vous l'appelez. Les tests d'intégration testent le comportement du code lorsqu'ils sont combinés ensemble en tant que système. Vous ne vous attendriez pas à ce que les tests unitaires évaluent le comportement du système, ni que les tests d'intégration vérifient les sorties spécifiques d'une méthode particulière.
Les tests unitaires, lorsqu'ils sont effectués correctement, sont plus faciles à configurer que les tests d'intégration. Si vous vous fiez uniquement aux tests d'intégration, vos tests vont:
Conclusion: utilisez les tests d'intégration pour vérifier que les connexions entre les objets fonctionnent correctement, mais appuyez-vous sur les tests unitaires premier pour vérifier les exigences fonctionnelles.
Cela dit, vous n'aurez peut-être pas besoin de tests unitaires pour certaines méthodes de référentiel. Les tests unitaires ne doivent pas être effectués sur les méthodes triviales; si tout ce que vous faites est de passer par une demande d'objet au code généré par ORM et de retourner un résultat, vous n'avez pas besoin de le tester unitaire, dans la plupart des cas; un test d'intégration est suffisant.
Je suis du côté des pragmatiques. Ne testez pas votre code deux fois.
Nous écrivons uniquement des tests d'intégration pour nos référentiels. Ces tests ne dépendent que d'une configuration de test simple qui s'exécute sur une base de données en mémoire. Je pense qu'ils fournissent tout ce qu'un test unitaire fait, et plus encore.
Les tests sont utiles lorsqu'ils se cassent: de manière proactive ou réactive.
Les tests unitaires sont proactifs et peuvent être une vérification fonctionnelle continue tandis que les tests d'intégration sont réactifs sur des données par étapes/fausses.
Si le système considéré est plus proche/dépend des données que de la logique de calcul, les tests d'intégration devraient avoir plus d'importance.
Par exemple, si nous construisons un système ETL, les tests les plus pertinents porteront sur les données (mises en scène, truquées ou en direct). Le même système aura quelques tests unitaires, mais uniquement autour de la validation, du filtrage, etc.
Le modèle de référentiel ne doit pas avoir de logique de calcul ou métier. Il est très proche de la base de données. Mais le référentiel est également proche de la logique métier qui l'utilise. Il s'agit donc de trouver un ÉQUILIBRE.
Les tests unitaires peuvent tester le comportement du référentiel. Les tests d'intégration peuvent tester CE QUI EST VRAIMENT ARRIVÉ lorsque le référentiel a été appelé.
Maintenant, "CE QUI EST VRAIMENT ARRIVÉ" peut sembler très attrayant. Mais cela pourrait être très coûteux pour une exécution cohérente et répétée. La définition classique des tests de fragilité s'applique ici.
Donc, la plupart du temps, je trouve juste assez bon pour écrire un test unitaire sur un objet qui utilise le référentiel. De cette façon, nous testons si la méthode de dépôt appropriée a été appelée et si les résultats Mocked appropriés sont renvoyés.
Les tests unitaires offrent un niveau de modularité que les tests d'intégration (par conception) ne peuvent pas. Lorsqu'un système est refactorisé ou recomposé, (et cela se produira se produit), les tests unitaires peuvent souvent être réutilisés, tandis que les tests d'intégration doivent souvent être réécrits. Les tests d'intégration qui essaient de faire le travail de quelque chose qui devrait être dans un test unitaire en font souvent trop, ce qui les rend difficiles à maintenir.
De plus, l'inclusion de tests unitaires présente les avantages suivants:
Il est possible de diviser votre travail (et d'appliquer une approche DRY) tout en utilisant à la fois les tests unitaires et les tests d'intégration. est déjà dans un test unitaire dans un test d'intégration, ce qui entraîne souvent moins de travail (et donc moins de retravail).
Il y a 3 choses distinctes qui doivent être testées: la procédure stockée, le code qui appelle la procédure stockée (c'est-à-dire votre classe de référentiel) et le consommateur. Le travail du référentiel consiste à générer une requête et à convertir l'ensemble de données renvoyé en objet de domaine. Il y a suffisamment de code pour prendre en charge les tests unitaires qui se distinguent de l'exécution réelle de la requête et de la création de l'ensemble de données.
Donc (c'est un exemple très très simplifié):
interface IOrderRepository
{
Order GetOrderByID(Guid id);
}
class OrderRepository : IOrderRepository
{
private readonly ISqlExecutor sqlExecutor;
public OrderRepository(ISqlExecutor sqlExecutor)
{
this.sqlExecutor = sqlExecutor;
}
public Order GetOrderByID(Guid id)
{
var sql = "SELECT blah, blah FROM Order WHERE OrderId = @p0";
var dataset = this.sqlExecutor.Execute(sql, p0 = id);
var result = this.orderFromDataset(dataset);
return result;
}
}
Ensuite, lorsque vous testez OrderRepository
, passez un ISqlExecutor
simulé et vérifiez que l'objet testé passe dans le bon SQL (c'est son travail) et retourne un objet Order
approprié étant donné certains jeu de données de résultat (également simulé). La seule façon de tester la classe concrète SqlExecutor
est probablement avec des tests d'intégration, c'est juste, mais c'est une classe wrapper mince et elle changera rarement, donc gros problème.
Vous devez également tester également vos procédures stockées.
Je peux réduire cela à une ligne de pensée très simple: favorisez les tests unitaires car cela aide à localiser les bogues. Et cela de manière cohérente car les incohérences provoquent de la confusion.
D'autres raisons découlent des mêmes règles qui guident le développement de OO puisqu'elles s'appliquent également aux tests. Par exemple, le principe de responsabilité unique.
Si certains tests unitaires semblent ne pas valoir la peine, alors c'est peut-être un signe que le sujet ne vaut vraiment pas la peine. Ou peut-être que sa fonctionnalité est plus abstraite que son homologue, et les tests pour elle (ainsi que son code) peuvent être abstraits à un niveau supérieur.
Comme pour tout, il y a des exceptions. Et la programmation est encore un peu une forme d'art, de sorte que de nombreux problèmes sont suffisamment différents pour justifier l'évaluation de chacun pour la meilleure approche.