2009-06-23 2 views
1

Possible en double:
Writing “unit testable” code?Comment concevoir une application de sorte qu'elle soit facilement testable à l'aide de tests unitaires?

Je suis nouveau à des tests unitaires. Aujourd'hui sur SO j'ai trouvé une réponse à une question sur interfaces in c#. Cela m'a fait réfléchir sur les meilleures pratiques pour la construction d'applications testables par conception. J'utilise les interfaces très légèrement dans l'application principale sur laquelle je travaille. Cela rend le test très difficile car je n'ai aucun moyen d'injecter des données de test dans la plupart des classes car chaque classe dépend directement d'autres classes, et donc des autres implémentations de classes. Donc, ma question est, pour une application C#, comment concevez-vous vos classes et implémentations, et quels outils utiliseriez-vous pour vous assurer que les méthodes et classes peuvent être testées indépendamment en fournissant des données de test pour chaque méthode ou classe.

EDIT: Plus précisément, si vous regardez la question à laquelle j'ai lié, la réponse utilise ceci: IWidget w = ObjectFactory.GetInstance();

Je n'ai jamais utilisé une classe/objet d'usine, donc je suis curieux de savoir comment cela fonctionne et comment il serait implémenté.

+0

Chris, cette question a été posée récemment: http://stackoverflow.com/questions/1007458/writing-unit-testable- code. C'était facile à trouver, juste à la recherche de "test unitaire". –

+0

Merci. J'ai fait une recherche rapide mais tous les résultats que j'ai vus concernaient des tests unitaires spécifiques. Désolé pour le doublon. –

Répondre

4

Dependency Injection est la solution que vous recherchez. Le problème général a à voir avec l'endroit où vos dépendances sont créées. Normalement, lorsque vous écrivez un programme orienté objet, l'instinct naturel est de créer vos dépendances directement en utilisant le nouveau mot-clé à l'endroit où vous avez besoin de vos dépendances. Parfois, vous pouvez créer des dépendances de longue durée dans un constructeur. Pour rendre vos classes plus testables unitairement, vous devez «inverser» cette norme et créer vos dépendances en externe (ou créer une fabrique/fournisseur/contexte qui peut à son tour être injectée et utilisée pour créer des instances d'autres dépendances) et "injecte" ces dépendances dans votre classe. Les deux mécanismes d'injection les plus courants sont soit des paramètres pour un constructeur, soit des propriétés avec des setters. En externalisant la gestion des dépendances de cette manière, vous pouvez facilement créer des versions simulées de ces dépendances et les transmettre, vous permettant de tester vos unités de code en les isolant complètement du reste de votre application.

Pour prendre en charge l'injection de dépendances et faciliter la gestion des conteneurs Inversion de contrôle (IoC) sont apparus. Un conteneur IoC est un framework qui vous permet de configurer des graphes de dépendance indépendamment des classes qui participent à ces graphes. Une fois qu'un graphe de dépendances est configuré (généralement enraciné dans la classe de clé unique), vous pouvez facilement créer des instances de vos objets lors de l'exécution sans avoir à vous soucier de créer manuellement toutes les dépendances requises. Cela permet de créer un code souple et flexible, facile à reconfigurer. Un exemple d'un très bon conteneur IoC est Castle Windsor, qui fournit un cadre très riche pour le câblage des classes via l'injection de dépendance.

Un exemple très simple d'injection de dépendance serait quelque chose comme ce qui suit:

interface ITaskService 
{ 
    void SomeOperation(); 
} 

interface IEntityService 
{ 
    Entity GetEntity(object key); 
    Entity Save(Entity entity); 
} 

class TaskService: ITaskService 
{ 
    public TaskService(EntityServiceFactory factory) 
    { 
     m_factory = factory; 
    } 

    private EntityServiceFactory m_factory; // Dependency 

    public void SomeOperation() // Method must be concurrent, so create new IEntityService each call 
    { 
     IEntityService entitySvc = m_factory.GetEntityService(); 
     Entity entity = entitySvc.GetEntity(...); 
     // Do some work with entity 
     entitySvc.Save(entity); 
    } 
} 

class EntityServiceFactory 
{ 
    public EntityServiceFactory(RepositoryProvider provider) 
    { 
     m_provider = provider; 
    } 

    private RepositoryProvider m_provider; // Dependency 

    public virtual IEntityService GetEntityService() 
    { 
     var repository = m_provider.GetRepository<Entity>(); 
     return new EntityService(repository); 
    } 
} 

class EntityService: IEntityService 
{ 
    public EntityService(IEntityRepository repository) 
    { 
     m_repository = repository; 
    } 

    private IEntityRepository m_repository; // Dependency 

    public Entity GetEntity(object key) 
    { 
     if (key == null) throw new ArgumentNullException("key"); 

     // TODO: Check for cached entity here? 

     Entity entity = m_repository.GetByKey(key); 
     return entity; 
    } 

    public Entity Save(Entity entity) 
    { 
     if (entity == null) throw new ArgumentNullException(entity); 

     if (entity.Key == null) 
     { 
      entity = m_repository.Insert(entity); 
     } 
     else 
     { 
      m_repository.Update(entity); 
     } 

     return entity; 
    } 
} 

class RepositoryProvider 
{ 
    public virtual object GetRepository<T>() 
    { 
     if (typeof(T) == typeof(Entity)) 
      return new EntityRepository(); 
     else if (...) 
      // ... etc. 
    } 
} 

interface IEntityRepository 
{ 
    Entity GetByKey(object key); 
    Entity Insert(Entity entity); 
    void Update(Entity entity); 
} 

class EntityRepository: IEntityRepository 
{ 
    public Entity GetByKey(object key) 
    { 
     // TODO: Load up an entity from a database here 
    } 

    public Entity Insert(Entity entity) 
    { 
     // TODO: Insert entity into database here 
    } 

    public void Update(Entity entity) 
    { 
     // TODO: Update existing entity in database here 
    } 
} 
+0

Merci pour cette explication très complète. Je dois absolument faire plus de recherches à ce sujet. –

+0

Votre accueil. :) Je n'ai pas eu l'occasion de faire mes tours StackOverflow depuis un moment ... vous avez eu l'avantage de ma première réponse dans quelques jours. – jrista

1

Dependency Injection est un excellent principe qui vous permet de créer des tests facilement en définissant mock objects si nécessaire. Fondamentalement, l'idée est que vous transmettez n'importe quelle dépendance à un objet, il ne crée pas ses propres objets à opérer.

-1

utilisent un cadre de simulation pour fournir des données de test

http://ayende.com/projects/rhino-mocks.aspx 
http://code.google.com/p/moq/