Comment créer des symboles imbriqués dans Sketch
Conception De L'interface Utilisateur
Il y a souvent beaucoup de confusion et de doute concernant les tests unitaires lors de la discussion avec les parties prenantes et les clients. Les tests unitaires ressemblent parfois à la soie dentaire pour un enfant, «Je me brosse déjà les dents, pourquoi dois-je faire ça?»
Suggérer des tests unitaires semble souvent une dépense inutile pour les personnes qui considèrent leurs méthodes de test et les tests d'acceptation par les utilisateurs suffisamment solides.
Mais les tests unitaires sont un outil très puissant et sont plus simples que vous ne le pensez. Dans cet article, nous examinerons les tests unitaires et les outils disponibles dans DotNet tels que Microsoft.VisualStudio.TestTools et Moq .
Nous allons essayer de construire une bibliothèque de classes simple qui calculera le nième terme de la séquence de Fibonacci. Pour ce faire, nous voudrons créer une classe pour le calcul des séquences de Fibonacci qui dépend d'une classe de mathématiques personnalisée qui additionne les nombres. Ensuite, nous pouvons utiliser le .NET Testing Framework pour nous assurer que notre programme s'exécute comme prévu.
Les tests unitaires décomposent le programme en le plus petit bit de code, généralement au niveau de la fonction, et garantissent que la fonction renvoie la valeur attendue. En utilisant un cadre de test unitaire, les tests unitaires deviennent une entité distincte qui peut ensuite exécuter des tests automatisés sur le programme au fur et à mesure de sa construction.
[TestClass] public class FibonacciTests { [TestMethod] //Check the first value we calculate public void Fibonacci_GetNthTerm_Input2_AssertResult1() { //Arrange int n = 2; //setup Mock mockMath = new Mock(); mockMath .Setup(r => r.Add(It.IsAny(), It.IsAny())) .Returns((int x, int y) => x + y); UnitTests.Fibonacci fibonacci = new UnitTests.Fibonacci(mockMath.Object); //Act int result = fibonacci.GetNthTerm(n); //Assert Assert.AreEqual(result, 1); } }
Un simple test unitaire utilisant la méthodologie Arrange, Act, Assert teste que notre bibliothèque mathématique peut ajouter correctement 2 + 2.
Une fois les tests unitaires configurés, si une modification est apportée au code, pour tenir compte d'une condition supplémentaire qui n'était pas connue lors du premier développement du programme, par exemple, les tests unitaires montreront si tous les cas correspondent aux valeurs attendues sortie par la fonction.
Le test unitaire est ne pas tests d'intégration. Il est ne pas tests de bout en bout. Bien que ces deux méthodes soient puissantes, elles devraient fonctionner en conjonction avec des tests unitaires, et non en remplacement.
L'avantage le plus difficile à comprendre des tests unitaires, mais le plus important, est la possibilité de retester le code modifié à la volée. La raison pour laquelle cela peut être si difficile à comprendre est que tant de développeurs pensent en eux-mêmes, «Je ne toucherai plus jamais à cette fonction» ou 'Je vais juste le tester à nouveau lorsque j'aurai terminé.' Et les parties prenantes pensent en termes de, «Si cet article est déjà écrit, pourquoi dois-je le tester à nouveau?»
quand adobe xd est-il sorti
En tant que personne qui a été des deux côtés du spectre du développement, j'ai dit ces deux choses. Le développeur à l'intérieur de moi sait pourquoi nous devons le tester à nouveau.
Les changements que nous apportons au quotidien peuvent avoir des impacts énormes. Par exemple:
Les tests unitaires prennent ces questions et les mémorisent dans le code et un processus pour s'assurer que ces questions reçoivent toujours une réponse. Les tests unitaires peuvent être exécutés avant une compilation pour s'assurer que vous n'avez pas introduit de nouveaux bogues. Les tests unitaires étant conçus pour être atomiques, ils sont exécutés très rapidement, généralement moins de 10 millisecondes par test. Même dans une très grande application, une suite de tests complète peut être réalisée en moins d'une heure. Votre processus UAT peut-il correspondre à cela?
Fibonacci_GetNthTerm_Input2_AssertResult1
qui est la première exécution et inclut le temps de configuration, tous les tests unitaires s'exécutent sous 5 ms. Ma convention de dénomination ici est configurée pour rechercher facilement une classe ou une méthode dans une classe que je souhaite tester
En tant que développeur, cela vous semble peut-être plus de travail. Oui, vous avez la certitude que le code que vous publiez est bon. Mais les tests unitaires vous offrent également la possibilité de voir où votre conception est faible. Écrivez-vous les mêmes tests unitaires pour deux morceaux de code? Devraient-ils plutôt être sur un seul morceau de code?
Obtenir votre code pour être testable unitaire lui-même est un moyen pour vous d'améliorer votre conception. Et pour la plupart des développeurs qui n’ont jamais effectué de tests unitaires ou qui ne prennent pas autant de temps pour examiner la conception avant le codage, vous pouvez réaliser à quel point votre conception s’améliore en la préparant pour les tests unitaires.
Outre DRY, nous avons également d'autres considérations.
Si vous devez écrire des tests unitaires trop complexes qui durent plus longtemps que prévu, votre méthode peut être trop compliquée et mieux adaptée en tant que méthodes multiples.
Si votre méthode testée nécessite une autre classe ou fonction, nous appelons cela une dépendance. Dans les tests unitaires, nous ne nous soucions pas de ce que la dépendance fait sous le capot; aux fins de la méthode testée, il s'agit d'une boîte noire. La dépendance a son propre ensemble de tests unitaires qui détermineront si son comportement fonctionne correctement.
En tant que testeur, vous souhaitez simuler cette dépendance et lui indiquer les valeurs à renvoyer dans des instances spécifiques. Cela vous donnera un meilleur contrôle sur vos cas de test. Pour ce faire, vous devrez injecter une version factice (ou comme nous le verrons plus tard, simulée) de cette dépendance.
Une fois que vous avez défini vos dépendances et votre injection de dépendances, vous constaterez peut-être que vous avez introduit des dépendances cycliques dans votre code. Si la classe A dépend de la classe B, qui à son tour dépend de la classe A, vous devez reconsidérer votre conception.
Prenons notre Exemple de Fibonacci . Votre patron vous dit qu'il a une nouvelle classe qui est plus efficace et précise que l'opérateur d'ajout actuel disponible en C #.
Bien que cet exemple particulier ne soit pas très probable dans le monde réel, nous voyons des exemples analogues dans d'autres composants, tels que l'authentification, le mappage d'objets et à peu près tout processus algorithmique. Dans le cadre de cet article, supposons simplement que la nouvelle fonction d’ajout de votre client est la plus récente et la plus performante depuis l’invention des ordinateurs.
En tant que tel, votre patron vous remet une bibliothèque boîte noire avec une seule classe Math
, et dans cette classe, une seule fonction Add
. Votre travail d'implémentation d'une calculatrice Fibonacci est susceptible de ressembler à ceci:
public int GetNthTerm(int n) { Math math = new Math(); int nMinusTwoTerm = 1; int nMinusOneTerm = 1; int newTerm = 0; for (int i = 2; i Ce n’est pas horrible. Vous instanciez un nouveau Math
classe et utilisez-le pour ajouter les deux termes précédents pour obtenir le suivant. Vous exécutez cette méthode via votre batterie normale de tests, calculez jusqu'à 100 termes, calculez le 1000e terme, le 10 000e terme, et ainsi de suite jusqu'à ce que vous soyez convaincu que votre méthodologie fonctionne correctement. Puis, à l’avenir, un utilisateur se plaint du fait que le 501e terme ne fonctionne pas comme prévu. Vous passez la soirée à parcourir votre code et à essayer de comprendre pourquoi ce cas de coin ne fonctionne pas. Vous commencez à vous méfier du dernier et du meilleur Math
la classe n’est pas aussi bonne que le pense votre patron. Mais c’est une boîte noire et vous ne pouvez pas vraiment le prouver - vous vous trouvez dans une impasse interne.
Le problème ici est que la dépendance Math
n'est pas injecté dans votre calculatrice Fibonacci. Par conséquent, dans vos tests, vous vous fiez toujours aux résultats existants, non testés et inconnus de Math
pour tester Fibonacci contre. S'il y a un problème avec Math
, alors Fibonacci sera toujours faux (sans coder un cas particulier pour le 501e terme).
L'idée pour corriger ce problème est d'injecter le Math
classe dans votre calculatrice Fibonacci. Mais encore mieux, c'est de créer une interface pour le Math
classe qui définit les méthodes publiques (dans notre cas, Add
) et implémente l'interface sur notre Math
classe.
public interface IMath { int Add(int x, int y); } public class Math : IMath { public int Add(int x, int y) { //super secret implementation here } } }
Plutôt que d'injecter le Math
classe dans Fibonacci, nous pouvons injecter le IMath
interface dans Fibonacci. L'avantage ici est que nous pourrions définir notre propre OurMath
classe que nous savons être précise et testez notre calculatrice par rapport à cela. Mieux encore, en utilisant Moq, nous pouvons simplement définir ce que Math.Add
Retour. Nous pouvons définir un certain nombre de sommes ou nous pouvons simplement dire Math.Add
pour renvoyer x + y.
private IMath _math; public Fibonacci(IMath math) { _math = math; }
Injectez l'interface IMath dans la classe Fibonacci
en essayant de résoudre rapidement un problème potentiellement compliqué, nous sommes plus susceptibles de compter sur
//setup Mock mockMath = new Mock(); mockMath .Setup(r => r.Add(It.IsAny(), It.IsAny())) .Returns((int x, int y) => x + y);
Utilisation de Moq pour définir quoi Math.Add
Retour.
Nous avons maintenant une méthode éprouvée (enfin, si l'opérateur + est faux en C #, nous avons des problèmes plus importants) pour ajouter deux nombres. En utilisant notre nouveau Mocked IMath
, nous pouvons coder un test unitaire pour notre 501e terme et voir si nous avons gaffé notre implémentation ou si le Math
personnalisé | la classe a besoin d'un peu plus de travail.
Ne laissez pas une méthode essayer d'en faire trop
Cet exemple souligne également l'idée d'une méthode en faisant trop. Bien sûr, l'addition est une opération assez simple sans trop avoir besoin d'abstraire ses fonctionnalités de notre GetNthTerm
méthode. Mais que se passerait-il si l'opération était un peu plus compliquée? Au lieu de l'ajout, il s'agissait peut-être de la validation du modèle, de l'appel d'une fabrique pour obtenir un objet sur lequel opérer ou de la collecte de données supplémentaires nécessaires à partir d'un référentiel.
La plupart des développeurs essaieront de s'en tenir à l'idée qu'une méthode a un seul but. Dans les tests unitaires, nous essayons de nous en tenir au principe selon lequel les tests unitaires devraient être appliqués aux méthodes atomiques et en introduisant trop d'opérations sur une méthode, nous la rendons non testable. Nous pouvons souvent créer un problème où nous devons écrire autant de tests pour tester correctement notre fonction.
Chaque paramètre que nous ajoutons à une méthode augmente le nombre de tests que nous devons écrire de manière exponentielle en fonction de la complexité du paramètre. Si vous ajoutez un booléen à votre logique, vous devez doubler le nombre de tests à écrire car vous devez maintenant vérifier les cas vrai et faux avec vos tests actuels. Dans le cas de la validation de modèle, la complexité de nos tests unitaires peut augmenter très rapidement.

Nous sommes tous coupables d’ajouter un petit plus à une méthode. Mais ces méthodes plus volumineuses et plus complexes créent le besoin d'un trop grand nombre de tests unitaires. Et il devient rapidement évident lorsque vous écrivez les tests unitaires que la méthode essaie d'en faire trop. Si vous pensez que vous essayez de tester trop de résultats possibles à partir de vos paramètres d'entrée, considérez le fait que votre méthode doit être divisée en une série de plus petits.
Ne vous répétez pas
Un de nos locataires préférés de la programmation. Celui-ci devrait être assez simple. Si vous vous retrouvez à écrire les mêmes tests plus d'une fois, vous avez introduit du code plus d'une fois. Il peut vous être utile de refactoriser ce travail dans une classe commune accessible aux deux instances que vous essayez de l’utiliser.
Quels outils de test unitaire sont disponibles?
DotNet nous offre une plate-forme de test unitaire très puissante prête à l'emploi. En utilisant cela, vous pouvez implémenter ce que l'on appelle le Méthodologie Organiser, Agir, Affirmer . Vous organisez vos considérations initiales, agissez sur ces conditions avec votre méthode à l'essai, puis affirmez que quelque chose s'est passé. Vous pouvez tout affirmer, ce qui rend cet outil encore plus puissant. Vous pouvez affirmer qu'une méthode a été appelée un certain nombre de fois, que la méthode a renvoyé une valeur spécifique, qu'un type particulier d'exception a été levé, ou tout ce à quoi vous pouvez penser. Pour ceux qui recherchent un framework plus avancé, NUnit et son homologue Java JUnit sont des options viables.
[TestMethod] //Test To Verify Add Never Called on the First Term public void Fibonacci_GetNthTerm_Input0_AssertAddNeverCalled() { //Arrange int n = 0; //setup Mock mockMath = new Mock(); mockMath .Setup(r => r.Add(It.IsAny(), It.IsAny())) .Returns((int x, int y) => x + y); UnitTests.Fibonacci fibonacci = new UnitTests.Fibonacci(mockMath.Object); //Act int result = fibonacci.GetNthTerm(n); //Assert mockMath.Verify(r => r.Add(It.IsAny(), It.IsAny()), Times.Never); }
Tester que notre méthode Fibonacci gère les nombres négatifs en lançant une exception. Les tests unitaires peuvent vérifier que l'exception a été levée.
Pour gérer l'injection de dépendances, les deux Ninject et Unité existent sur la plateforme DotNet. Il y a très peu de différence entre les deux, et cela devient une question de savoir si vous voulez gérer des configurations avec Fluent Syntax ou XML Configuration.
Pour simuler les dépendances, je recommande Moq. Moq peut être difficile à mettre la main, mais l'essentiel est que vous créez une version simulée de vos dépendances. Ensuite, vous indiquez à la dépendance ce qu'elle doit renvoyer dans des conditions spécifiques. Par exemple, si vous aviez une méthode nommée Square(int x)
qui a mis l'entier au carré, vous pourriez lui dire quand x = 2, retourner 4. Vous pouvez également lui dire de retourner x ^ 2 pour n'importe quel entier. Ou vous pouvez lui dire de renvoyer 5 lorsque x = 2. Pourquoi exécuteriez-vous le dernier cas? Dans le cas où la méthode sous le rôle du test est de valider la réponse de la dépendance, vous pouvez forcer le retour des réponses invalides pour vous assurer que vous attrapez correctement le bogue.
[TestMethod] //Test To Verify Add Called Three times on the fifth Term public void Fibonacci_GetNthTerm_Input4_AssertAddCalledThreeTimes() { //Arrange int n = 4; //setup Mock mockMath = new Mock(); mockMath .Setup(r => r.Add(It.IsAny(), It.IsAny())) .Returns((int x, int y) => x + y); UnitTests.Fibonacci fibonacci = new UnitTests.Fibonacci(mockMath.Object); //Act int result = fibonacci.GetNthTerm(n); //Assert mockMath.Verify(r => r.Add(It.IsAny(), It.IsAny()), Times.Exactly(3)); }
Utilisation de Moq pour dire aux moqués IMath
interface comment gérer Add
sous test. Vous pouvez définir des cas explicites avec It.Is
ou une plage avec It.IsInRange
.
Cadres de tests unitaires pour DotNet
Cadre de test unitaire Microsoft
La Cadre de test unitaire Microsoft est la solution de test unitaire prête à l'emploi de Microsoft et incluse avec Visual Studio. Parce qu'il est livré avec VS, il s'intègre bien avec lui. Lorsque vous commencez un projet, Visual Studio vous demande si vous souhaitez créer une bibliothèque de tests unitaires à côté de votre application.
Le Microsoft Unit Testing Framework est également fourni avec un certain nombre d'outils pour vous aider à mieux analyser vos procédures de test. De plus, comme il appartient et est écrit par Microsoft, il y a un certain sentiment de stabilité dans son existence à l'avenir.
Mais lorsque vous travaillez avec les outils Microsoft, vous obtenez ce qu'ils vous offrent. Le cadre de test unitaire de Microsoft peut être fastidieux à intégrer.
comment faire un serveur avec un raspberry pi
NUnit
Le plus gros avantage pour moi en utilisant NUnit est des tests paramétrés. Dans notre exemple de Fibonacci ci-dessus, nous pouvons entrer un certain nombre de cas de test et nous assurer que ces résultats sont vrais. Et dans le cas de notre 501e problème, nous pouvons toujours ajouter un nouveau jeu de paramètres pour nous assurer que le test est toujours exécuté sans avoir besoin d'une nouvelle méthode de test.
L'inconvénient majeur de NUnit est de l'intégrer dans Visual Studio. Il manque les cloches et les sifflets fournis avec la version Microsoft et signifie que vous devrez télécharger votre propre ensemble d'outils.
xUnit.Net
xUnit est très populaire en C # car il s'intègre parfaitement à l'écosystème .NET existant. Nuget a de nombreuses extensions de xUnit disponibles. Il s'intègre également parfaitement à Team Foundation Server, même si je ne sais pas combien Développeurs .NET utilisent toujours TFS sur diverses implémentations Git.
lever des capitaux pour la création d'entreprise
En revanche, de nombreux utilisateurs se plaignent que la documentation de xUnit fait un peu défaut. Pour les nouveaux utilisateurs de tests unitaires, cela peut causer un énorme mal de tête. En outre, l’extensibilité et l’adaptabilité de xUnit rendent également la courbe d’apprentissage un peu plus abrupte que celle de NUnit ou du cadre de test unitaire de Microsoft.
Conception / développement pilotés par les tests
Conception / développement pilotés par les tests (TDD) est un sujet un peu plus avancé qui mérite son propre article. Cependant, je voulais faire une introduction.
L'idée est de commencer par vos tests unitaires et de dire à vos tests unitaires ce qui est correct. Ensuite, vous pouvez écrire votre code autour de ces tests. En théorie, le concept semble simple, mais en pratique, il est très difficile d’entraîner votre cerveau à réfléchir à l’application. Mais cette approche a l'avantage de ne pas être obligé d'écrire vos tests unitaires après coup. Cela conduit à moins de refactoring, de réécriture et de confusion de classe.
TDD a été un peu un mot à la mode ces dernières années, mais son adoption a été lente. Sa nature conceptuelle est déroutante pour les parties prenantes, ce qui rend difficile son approbation. Mais en tant que développeur, je vous encourage à écrire même une petite application en utilisant l'approche TDD pour vous habituer au processus.
Pourquoi vous ne pouvez pas avoir trop de tests unitaires
Les tests unitaires sont l'un des outils de test les plus puissants dont les développeurs disposent. Il n'est en aucun cas suffisant pour un test complet de votre application, mais ses avantages en matière de tests de régression, de conception de code et de documentation sur les objectifs sont inégalés.
Il n'y a rien de tel qu'écrire trop de tests unitaires. Chaque cas de bord peut proposer de gros problèmes sur toute la ligne de votre logiciel. La mémorisation des bogues trouvés sous forme de tests unitaires peut garantir que ces bogues ne trouvent pas de moyen de se réinsérer dans votre logiciel lors des modifications de code ultérieures. Bien que vous puissiez ajouter 10 à 20% au budget initial de votre projet, vous pourriez économiser beaucoup plus que cela en formation, en corrections de bogues et en documentation.
Vous pouvez trouver le référentiel Bitbucket utilisé dans cet article Ici .
Comprendre les bases
Qu'entendez-vous par test unitaire?
Test unitaire du processus de test des méthodes individuelles. Les tests unitaires garantissent que chaque composant agit comme prévu.
Qu'est-ce qu'un exemple de test unitaire?
Test unitaire Le processus de test d'une fonction atomique en passant des valeurs connues dans cette fonction et en affirmant qu'un résultat attendu est produit par la fonction.
Comment les tests unitaires sont-ils effectués?
Les tests unitaires sont effectués à l'aide d'un cadre et d'un ensemble d'outils souvent adaptés au cadre de votre base de code.
Qu'est-ce qu'un bon test unitaire?
Un bon test unitaire est celui qui englobe toutes les entrées possibles dans votre méthode. Les résultats de ces méthodes doivent être connus du concepteur et le test unitaire doit refléter le résultat attendu.
Quels sont les avantages des tests unitaires?
Les tests unitaires aident à garantir la qualité de votre code en vous obligeant à écrire du code testable, ils garantissent la fonctionnalité de votre code et constituent un excellent point de départ pour les tests de régression.
Les tests unitaires en valent-ils la peine?
Oui. Les tests unitaires peuvent ajouter un peu à vos coûts de développement, mais à long terme, ils vous feront gagner du temps et des efforts grâce aux tests de régression et à la mémorisation des bogues.
Combien de tests unitaires dois-je écrire?
Idéalement, vos tests unitaires devraient couvrir tous les aspects de votre code dans toutes les possibilités. Bien sûr, en réalité, cela est impossible et la réponse simple est que vous ne pouvez jamais écrire trop de tests unitaires.