TDD avec modèle de référentiel


10

Dans mon nouveau projet, j'ai décidé d'essayer avec TDD. Et au tout début, j'ai rencontré un problème. La première chose que je veux faire dans mon application est de donner la possibilité de lire des données à partir d'une source de données. À cette fin, je souhaite utiliser le modèle de référentiel. Et maintenant:

  • Si le test concerne l'implémentation réelle de l'interface du référentiel, je testerai la classe qui a accès à la base de données, et je sais que je devrais éviter cela.
  • Si les tests ne concernent pas la mise en œuvre réelle du modèle de référentiel, je testerai bien ... juste une simulation. Aucun code de production ne sera testé dans ces tests unitaires.

J'y pense depuis deux jours et je n'arrive toujours pas à trouver de solution raisonnable. Ce que je devrais faire?

Réponses:


11

Ce que fait un référentiel, c'est traduire de votre domaine vers votre infrastructure DAL, comme NHibernate ou Doctrine, ou vos classes exécutant SQL. Cela signifie que votre référentiel appellera des méthodes sur ledit framework pour effectuer ses tâches: votre référentiel construit les requêtes nécessaires pour récupérer les données. Si vous n'utilisez pas de cadre ORM (j'espère que vous l'êtes ...), le référentiel serait l'endroit où les instructions SQL brutes sont construites.

La plus fondamentale de ces méthodes est la sauvegarde: dans la plupart des cas, cela passera simplement l'objet du référentiel à l'unité de travail (ou à la session).

public void Save(Car car)
{
    session.Save(car);
}

Mais regardons un autre exemple, par exemple aller chercher une voiture par son ID. Cela pourrait ressembler

public function GetCarWithId(String id)
{
    return Session.QueryOver<Car>()
                    .Where(x => x.Id == id)
                    .SingleOrDefault();
}

Ce n'est pas encore trop complexe, mais vous pouvez imaginer avec de multiples conditions (faites-moi toutes les voitures fabriquées après 2010 pour toutes les marques du groupe 'Volkswagen') cela devient difficile. Donc, en vrai mode TDD, vous devez tester cela. Il y a plusieurs moyens de le faire.

Option 1: se moquer des appels passés au cadre ORM

Bien sûr, vous pouvez vous moquer de l'objet Session et simplement affirmer que les bons appels sont effectués. Bien que cela teste le référentiel, il n'est pas vraiment piloté par les tests car vous testez simplement que le référentiel ressemble en interne à ce que vous voulez. Le test dit essentiellement que "le code devrait ressembler à ceci". C'est quand même une approche valable, mais on a l'impression que ce genre de test a très peu de valeur.

Option 2: (Re) construire la base de données à partir des tests

Certains frameworks DAL vous permettent de créer la structure complète de la base de données sur la base des fichiers de mappage que vous créez pour mapper le domaine sur les tables. Pour ces cadres, le moyen de tester les référentiels consiste souvent à créer la base de données avec une base de données en mémoire lors de la première étape du test et à ajouter des objets à l'aide du cadre DAL à la base de données en mémoire. Après cela, vous pouvez utiliser le référentiel sur la base de données en mémoire pour tester si les méthodes fonctionnent. Ces tests sont plus lents, mais très valables et conduisent vos tests. Cela nécessite une certaine coopération de votre cadre DAL.

Option 3: test sur une base de données réelle

Une autre approche consiste à tester sur une base de données réelle et à isoler les plus instables. Vous pouvez le faire de plusieurs manières: entourez vos tests d'une transaction, nettoyez manuellement (ne recommande pas car très difficile à maintenir), reconstruisez complètement la base de données après chaque étape ... Selon l'application que vous construisez, cela peut ou peut pas faisable. Dans mes applications, je peux complètement créer une base de données de développement local à partir du contrôle de code source et mes tests sur référentiels utilisent des transactions pour isoler complètement les tests les uns des autres (transaction ouverte, insérer des données, référentiel de test, transaction de restauration). Chaque build configure d'abord la base de données de développement locale, puis effectue des tests isolés des transactions pour les référentiels sur cette base de données de développement locale. Il'

Ne testez pas le DAL

Si vous utilisez un framework DAL tel que NHibernate, évitez d'avoir à tester ce framework. Vous pouvez tester vos fichiers de mappage en enregistrant, en récupérant puis en comparant un objet de domaine pour vous assurer que tout va bien (assurez-vous de désactiver toute sorte de mise en cache), mais ce n'est pas aussi requis que de nombreux autres tests que vous devriez écrire. J'ai tendance à le faire principalement pour les collections sur les parents avec des conditions sur les enfants.

Lorsque vous testez le retour de vos référentiels, vous pouvez simplement vérifier si certaines propriétés d'identification sur votre objet de domaine correspondent. Cela peut être un identifiant, mais dans les tests, il est souvent plus avantageux de vérifier une propriété lisible par l'homme. Dans la section «obtenez-moi toutes les voitures fabriquées après 2010 ...», cela pourrait simplement vérifier que cinq voitures sont retournées et que les plaques d'immatriculation sont «insérer la liste ici». L'avantage supplémentaire est qu'il vous oblige à penser au tri ET que votre test force automatiquement le tri. Vous seriez surpris du nombre d'applications triant plusieurs fois (retour trié à partir de la base de données, tri avant de créer un objet de vue, puis tri de l'objet de vue, le tout sur la même propriété au cas où ), ou supposant implicitement le tri du référentiel et suppression accidentelle qui étaient en cours de route, brisant l'interface utilisateur.

«Test unitaire» n'est qu'un nom

À mon avis, les tests unitaires ne devraient généralement pas toucher la base de données. Vous créez une application de sorte que chaque morceau de code qui a besoin de données d'une source le fasse avec un référentiel, et ce référentiel est injecté en tant que dépendance. Cela permet une moquerie facile et toute la bonté TDD que vous voulez. Mais à la fin, vous voulez vous assurer que vos référentiels remplissent leurs fonctions et si la façon la plus simple de le faire est de toucher une base de données, tant pis. J'ai depuis longtemps abandonné l'idée que «les tests unitaires ne devraient pas toucher la base de données» et j'ai appris qu'il y a de très réelles raisons de le faire. Mais seulement si vous pouvez le faire automatiquement et à plusieurs reprises. Et la météo que nous appelons un tel test un «test unitaire» ou un «test d'intégration» est sans objet.


3
Les tests unitaires et les tests d'intégration ont des objectifs différents. Les noms de ces tests ne sont pas simplement décoratifs; ils sont également descriptifs.
Robert Harvey

9
  1. Ne testez pas les méthodes de référentiel triviales ou évidentes.

    Si les méthodes sont des opérations CRUD triviales, tout ce que vous testez réellement est de savoir si les paramètres sont correctement mappés. Si vous avez des tests d'intégration, ces erreurs deviendront de toute façon immédiatement apparentes.

    C'est le même principe qui s'applique aux propriétés triviales, comme celle-ci:

    public property SomeProperty
    {
        get { return _someProperty; }
        set { _someProperty = value; }
    }
    

    Vous ne le testez pas, car il n'y a rien à tester. Il n'y a aucune validation ou autre logique dans la propriété qui doit être vérifiée.

  2. Si vous voulez toujours tester ces méthodes ...

    Les simulacres sont le moyen de le faire. N'oubliez pas que ce sont des tests unitaires. Vous ne testez pas la base de données avec des tests unitaires; c'est à cela que servent les tests d'intégration.

Plus d'informations
The Full Stack, Part 3: Building a Repository using TDD (commencez à regarder à environ 16 minutes).


3
Bien sûr, je comprends cela. Pourtant, si c'est l'approche TDD, je ne devrais pas écrire de code si je n'ai pas de tests pour ce code d'abord, non?
Thaven

1
@Thaven - il y a une série de vidéos sur YouTube intitulée "est-ce que tdd est mort?". Regarde-les. Ils abordent de nombreux points intéressants, dont la notion selon laquelle appliquer TDD à tous les niveaux de votre application n'est pas nécessairement la meilleure idée. "pas de code sans échec" est une position trop extrême, est l'une des conclusions.
Jules

2
@Jules: Quel est le tl; dw?
Robert Harvey

1
@RobertHarvey C'est difficile à résumer, mais le point le plus important était que traiter le TDD comme une religion qui doit toujours être respectée est une erreur. Le choix de l'utiliser fait partie d'un compromis et vous devez considérer que (1) vous pourrez peut-être travailler plus rapidement sans lui sur certains problèmes et (2) cela peut vous pousser vers une solution plus complexe que celle dont vous avez besoin, surtout si vous vous retrouvez à utiliser beaucoup de simulacres.
Jules

1
+1 pour le point # 1. Les tests peuvent être erronés, c'est juste qu'ils sont généralement triviaux. Il est inutile de tester une fonction dont l'exactitude est plus évidente que celle du test. Ce n'est pas comme obtenir une couverture de code à 100% vous permettant de tester toutes les exécutions possibles du programme, vous pourriez donc aussi bien savoir où vous dépensez vos efforts de test.
Doval
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.