Comment puis-je commencer à utiliser TDD pour coder des fonctionnalités simples?

9

J'ai essentiellement l'essentiel de TDD. Je suis convaincu que c'est utile et j'ai une maîtrise raisonnable du framework MSTEST. Cependant, à ce jour, je n'ai pas réussi à l'utiliser comme méthode de développement primaire. Surtout, je l'utilise comme substitut pour écrire des applications de console en tant que pilotes de test (mon approche traditionnelle).

La chose la plus utile à ce sujet pour moi est la façon dont il absorbe le rôle des tests de régression.

Je n'ai encore rien construit qui isole spécifiquement divers comportements testables, ce qui est une autre grande partie de l'image que je connais.

Cette question consiste donc à demander des conseils sur les premiers tests que je pourrais écrire pour la tâche de développement suivante: Je veux produire du code qui encapsule l'exécution des tâches à la manière du producteur / consommateur.

Je me suis arrêté et j'ai décidé d'écrire cette question après avoir écrit ce code (je me demandais si je pouvais réellement utiliser TDD pour de vrai cette fois)

Code:

interface ITask
{
    Guid TaskId { get; }
    bool IsComplete { get; }
    bool IsFailed { get; }
    bool IsRunning { get; }
}

interface ITaskContainer
{
    Guid AddTask(ICommand action);
}

interface ICommand
{
    string CommandName { get; }
    Dictionary<string, object> Parameters { get; }
    void Execute();
}
Aaron Anodide
la source
Vous devriez d'abord avoir écrit les tests et ensuite les interfaces! L'idée est que TDD est pour votre API.
1
Comment écrire des tests pour des interfaces qui n'existent pas encore? Ils ne compileront même pas.
Robert Harvey
5
C'est votre premier test qui échoue.
cori

Réponses:

10

À partir de ce concept:
1) Commencez par le comportement que vous désirez. Écrivez un test pour cela. Voir échec du test.
2) Écrivez suffisamment de code pour réussir le test. Voir tous les tests réussis.
3) Recherchez le code redondant / bâclé -> refactor. Voir les tests réussissent toujours. Goto 1

Donc, sur # 1, disons que vous voulez créer une nouvelle commande (je m'étends sur la façon dont la commande fonctionnerait, alors restez avec moi). (De plus, je serai un peu pragmatique plutôt que TDD extrême)

La nouvelle commande est appelée MakeMyLunch, vous devez donc d'abord créer un test pour l'instancier et obtenir le nom de la commande:

@Test
public void instantiateMakeMyLunch() {
   ICommand command = new MakeMyLunchCommand();
   assertEquals("makeMyLunch",command.getCommandName());
}

Cela échoue, vous obligeant à créer la nouvelle classe de commandes et à lui faire retourner son nom (puriste dirait que c'est deux tours de TDD, pas 1). Vous créez donc la classe et lui faites implémenter l'interface ICommand, y compris en renvoyant le nom de la commande. L'exécution de tous les tests montre maintenant que tous les tests ont réussi, vous devez donc rechercher des opportunités de refactoring. Probablement aucun.

Ensuite, vous voulez qu'il implémente l'exécution. Il faut donc se demander: comment savoir si "MakeMyLunch" a "réussi mon déjeuner". Quels changements dans le système à cause de cette opération? Puis-je tester cela?

Supposons qu'il soit facile de tester:

@Test
public void checkThatMakeMyLunchIsSuccessful() {
   ICommand command = new MakeMyLunchCommand();
   command.execute();
   assertTrue( Lunch.isReady() );
}

D'autres fois, c'est plus difficile, et ce que vous voulez vraiment faire, c'est tester les responsabilités du sujet à tester (MakeMyLunchCommand). Peut-être que la responsabilité de MakeMyLunchCommand est d'interagir avec le réfrigérateur et le micro-ondes. Donc, pour le tester, vous pouvez utiliser un faux réfrigérateur et un faux micro-ondes. [deux exemples de frameworks simulés sont Mockito et nMock ou regardez ici .]

Dans ce cas, vous feriez quelque chose comme le pseudo-code suivant:

@Test
public void checkThatMakeMyLunchIsSuccessful() {
   Fridge mockFridge = mock(Fridge);
   Microwave mockMicrowave = mock(Microwave);
   ICommand command = new MakeMyLunchCommand( mockFridge, mockMicrowave );
   command.execute();
   mockFramework.assertCalled( mockFridge.removeFood );
   mockFramework.assertCalled( microwave.turnon );
}

Le puriste dit tester la responsabilité de votre classe - ses interactions avec les autres classes (la commande a-t-elle ouvert le réfrigérateur et allumé le micro-ondes?).

Le pragmatiste dit de tester un groupe de classes et de tester le résultat (votre déjeuner est-il prêt?).

Trouvez le bon équilibre qui fonctionne pour votre système.

(Remarque: considérez que vous êtes peut-être arrivé à votre structure d'interface trop tôt. Peut-être pouvez-vous laisser cela évoluer lorsque vous écrivez vos tests unitaires et implémentations, et à l'étape 3, vous "remarquez" l'opportunité d'interface commune).

jayraynet
la source
si je n'avais pas pré-écrit mon interface, quelle question aurait conduit à la création de la méthode Execute () - certaines de mes premières tentatives de TDD ont été bloquées alors que je n'avais pas "d'étape" pour stimuler des fonctionnalités supplémentaires - j'obtiens la sensation est un problème latent de poulet / oeuf qui doit être évité
Aaron Anodide
1
Bonne question! Si la seule commande que vous avez effectuée était "MakeMyLunchCommand", la méthode peut avoir démarré avec ".makeMyLunch ()". Ce qui aurait été bien. Ensuite, vous effectuez une autre commande ("NapCommand.takeNap ()"). Toujours pas de problème avec différentes méthodes. Ensuite, vous commencez à l'utiliser dans votre écosystème, ce qui est probablement l'endroit où vous êtes obligé de généraliser dans l'interface ICommand. En général, vous retardez souvent la généralisation jusqu'au dernier moment responsable, car YAGNI [ en.wikipedia.org/wiki/You_ain't_gonna_need_it ] =) D'autres fois, vous savez que vous allez y arriver, alors vous commencez avec.
jayraynet
(On suppose également ici que vous utilisez un IDE moderne qui rend la refactorisation des choses comme les noms de méthode triviale)
jayraynet
1
merci encore pour les conseils, c'est une sorte de jalon pour moi de voir enfin toutes les pièces et comment elles s'adaptent - et oui, un refactor rapide est dans ma boîte à outils
Aaron Anodide