2009-07-24 10 views
4

Je n'ai jamais écrit de tests unitaires auparavant, pour diverses raisons. J'ai maintenant la chance d'écrire des tests, confortablement, parce que j'ai une petite application à faire de toutes pièces.Comment appliqueriez-vous les tests unitaires dans cette situation?

Cependant, je suis un peu perplexe. L'application est supposée utiliser une imprimante avec un lecteur de carte à puce pour programmer des données sur une carte à puce. Voici donc la séquence des actions: créer le contexte de l'appareil, définir le mode de l'imprimante, initialiser un document, insérer une carte dans l'imprimante, se connecter à la carte avec un lecteur, écrire quelque chose sur la carte, sortir la carte, contexte de l'appareil. Bon, les tests unitaires sont censés tester une fonction pour chaque test, et chaque test est supposé fonctionner indépendamment du résultat d'autres tests. Mais voyons - je ne peux pas tester l'écriture sur une carte à puce si je ne l'ai pas positionnée correctement dans l'imprimante et si je ne me suis pas connectée à celle-ci. Et je ne peux pas me moquer de cela par un logiciel - je ne peux que tester si l'écriture s'est effectivement produite si la vraie carte est correctement positionnée et connectée. Et si la connexion à la carte échoue, il n'y a aucun moyen de tester l'écriture sur la carte - le principe d'indépendance du test est donc rompu.

Jusqu'à présent, je suis venu avec un test comme celui-ci (il y a aussi d'autres tests qui sont « bon » et tester d'autres choses)

[Test] 
public void _WriteToSmartCard() 
{ 
//start print job 
printer = new DataCardPrinter(); 
reader = new SCMSmartCardReader(); 
di = DataCardPrinter.InitializeDI(); 
printer.CreateHDC(); 
Assert.AreNotEqual(printer.Hdc, 0, "Creating HDC Failed"); 
Assert.Greater(di.cbSize, 0); 

int res = ICE_API.SetInteractiveMode(printer.Hdc, true); 
Assert.Greater(res, 0, "Interactive Mode Failed"); 

res = ICE_API.StartDoc(printer.Hdc, ref di); 
Assert.Greater(res, 0, "Start Document Failed"); 

res = ICE_API.StartPage(printer.Hdc); 
Assert.Greater(res, 0, "Start Page Failed"); 

res = ICE_API.RotateCardSide(printer.Hdc, 1); 
Assert.Greater(res, 0, "RotateCardSide Failed"); 

res = ICE_API.FeedCard(printer.Hdc, ICE_API.ICE_SMARTCARD_FRONT + ICE_API.ICE_GRAPHICS_FRONT); 
Assert.Greater(res, 0, "FeedCard Failed"); 

bool bRes = reader.EstablishContext(); 
Assert.True(bRes, "EstablishContext Failed"); 

bRes = reader.ConnectToCard(); 
Assert.True(bRes, "Connect Failed"); 

bRes = reader.WriteToCard("123456"); 
Assert.True(bRes, "Write To Card Failed"); 

string read = reader.ReadFromCard(); 
Assert.AreEqual("123456", read, "Read From Card Failed"); 

bRes = reader.DisconnectFromCard(); 
Assert.True(bRes, "Disconnect Failde"); 

res = ICE_API.SmartCardContinue(printer.Hdc, ICE_API.ICE_SMART_CARD_GOOD); 
Assert.Greater(res, 0, "SmartCardContinue Failed"); 

res = ICE_API.EndPage(printer.Hdc); 
Assert.Greater(res, 0, "End Page Failed"); 

res = ICE_API.EndDoc(printer.Hdc); 
Assert.Greater(res, 0, "End Document Failed"); 
} 

Le test fonctionne, mais les principes sont brisés - il teste plusieurs fonctions, et beaucoup d'entre elles. Et chaque fonction suivante dépend du résultat de la précédente. Maintenant, nous arrivons à la question: Comment dois-je aborder les tests unitaires dans ces circonstances?

+0

Une chance d'accéder à l'emballage C#? Nous faisons la même chose mais en recourant au code VB6 et C++. J'adorerais pouvoir tout faire dans un meilleur IDE et cadre. – fuzz

Répondre

4

Votre code de test est ce qu'on appelle souvent un test d'intégration. En résumé, les tests d'intégration sont souvent définis comme des tests qui vérifient l'intégration entre les composants d'un système. Bien que, comme le mentionne David Reis, les tests unitaires testent souvent des méthodes individuelles.

Les deux classes de tests sont utiles. Les tests d'intégration, comme le vôtre, exercent le système du début à la fin en s'assurant que tout fonctionne bien ensemble. Mais ils sont lents et ont souvent des dépendances extérieures (comme un lecteur de carte). Les tests unitaires sont plus petits, plus rapides et très ciblés, mais il est difficile de voir la forêt pour les arbres si tout ce que vous avez sont des tests unitaires.

Placez vos tests unitaires dans un répertoire distinct de vos tests d'intégration. Utilisez l'intégration continue. Exécutez vos tests d'intégration peut-être seulement quelques fois par jour, car ils sont plus lents et nécessitent plus d'installation/déploiement. Exécutez vos tests unitaires tout le temps.

Maintenant, comment testez-vous votre situation particulière où les méthodes dépendent d'autres méthodes? La quantité de code que vous contrôlez n'est pas claire, contrairement à ce qui se passe dans les bibliothèques, mais dans votre code, apprenez à utiliser autant que possible l'injection de dépendances (DI).

Supposons que votre méthode de lecture ressemble à quelque chose comme ça (en pseudocode)

boolean WriteToCard(String data){ 
    // do something to data here 
    return ICE_API.WriteToCard(ICE_API.SOME_FLAG, data) 
} 

Eh bien, vous devriez être en mesure de changer cela à quelque chose comme:

ICE_API api = null 

    ICE_API setApi(ICE_API api) { 
     this.api = api 
    } 

    ICE_API getApi() { 
     if (api == null) { 
     api = new ICE_API() 
     } 
    } 

    boolean WriteToCard(String data){ 
     // do something to data here  
     return getApi().WriteToCard(ICE_API.SOME_FLAG, data) 
    } 

Ensuite, dans votre test pour WriteToCard en l'installation que vous feriez

void setup() 
    _mockAPI = new Mock(ICE_API) 
    reader.setApi(_mockAPI) 

void testWriteToCard() 
    reader.writeToCard("12345") 
    // assert _mockAPI.writeToCard was called with expected data and flags. 
+0

Désolé, je ne sais pas C# il peut y avoir de meilleurs moyens de faire DI . De plus, dans java, faire du DI avec des méthodes statiques de type sucks, vous pouvez envelopper les appels statiques dans une méthode puis dans votre stub de test, ces méthodes wrapper pour faire vos assertions. –

+0

Les choses moqueuses ne semblent pas être utiles ici: je ne peux pas tester l'écriture sur la carte si je me moque de la position de la carte. La carte doit être physiquement présente. Je dépends donc de l'imprimante allumée, de la carte correctement alimentée, etc. avant de pouvoir vraiment tester 'WriteToCard'. Je suppose que je vais appeler ce test un test d'intégration et le séparer du reste. – Evgeny

3

Il n'y a rien par nature erroné avec une série de tests qui dépendent les uns des autres, sauf que vous n'obtiendrez pas une liste complète des échecs si plusieurs choses sont brisées, parce que le premier test échouer sera le un signalé. Vous pouvez toutefois résoudre ce problème en créant une routine d'initialisation de test (en utilisant l'attribut [SetUp] de votre classe [TestFixture]) qui met le système dans un état connu avant de procéder à un test unique. Notez également que ce scénario n'est pas entièrement adapté aux tests unitaires, car il nécessite des étapes manuelles potentielles de logiciel. Les tests unitaires sont intrinsèquement mieux adaptés au test de modules logiciels qui n'interagissent pas avec des éléments non reproductibles. Vous pouvez rendre les opérations sur l'API du lecteur abstraites (en créant une interface pour les opérations dont vous avez besoin, et une classe qui transmet ces appels à l'API réelle), puis vous pouvez utiliser un objet simulé pour prétendre être le lecteur de sorte que vous pouvez tester la logique principale de votre classe (s) sans avoir à dépendre du matériel.

Ensuite, vous pouvez implémenter le test de l'API réelle réelle, soit dans un test unitaire, ou dans quelque chose qui nécessite une interaction humaine minimale à faire ... fondamentalement, vous encapsuler l'humain dans votre processus de test;

4

Cela ne ressemble pas à un test unitaire. Le test unitaire doit être rapide et assertif, c'est-à-dire que vous ne devriez pas avoir besoin de vérifier si une opération s'est bien déroulée dans un matériel. Je classerais ce code comme "automatisation de test", car vous devez exécuter cette tâche et être sûr que quelque chose s'est passé.

Le code est également procédural et semble difficile à tester. L'utilisation de plusieurs assertions dans la même méthode de test indique qu'elle devrait être divisée.

Ma référence préférée pour les tests unitaires est Misko Hevery's site. J'espère que cela aide!

+0

Eh bien c'est ce que ma question était - ce n'est pas vraiment un test unitaire! J'ai d'autres tests qui ressemblent plus à des tests unitaires, par exemple [Test] ConverLongStringToHexArray public void() { octet [] prévu = new byte [] {0x31, 0x32, 0x33, 0x34, 0x35, 0x31, 0x32, 0x33, 0x34, 0x35, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36}; byte [] resultat = lecteur.StringToHexArray ("1234512345123456zzzzzzzzzzzzzzzzzzzzzzzzz"); Assert.AreEqual (attendu, résultat, "Erreur lors de la conversion d'une chaîne longue en tableau d'octets"); } Merci pour le lien btw, très utile – Evgeny

+0

Placer le code dans les commentaires est une mauvaise idée ... – Evgeny