2010-07-01 19 views
3

J'essaie d'utiliser SUT Factory 'pattern' pour créer mon SUT.Système TDD sous test Modèles de création (AutoFixture)

Compte tenu de la structure SUT:

namespace MySut 
{ 
    public class Dep1 
    { 
    } 

    public class Dep2 
    { 
    } 

    public class Sut 
    { 
     public Sut(Dep1 dep1, Dep2 dep2) 
     { 
     } 
    } 
} 

J'utilise AutoFixture, et je me demande quelle est la meilleure façon de s'effondrer les spécifications suivantes et la méthode d'usine SUT associée [précieuse mais] busywork:

namespace MySpecifications 
{ 
    using MySut; 
    public class MySpecification 
    { 
     public void TestCore() 
     { 
      // Dont care about dependencies, testing core functionality 
      var sut = CreateSut(); 
     } 

     public void TestDep1Interaction() 
     { 
      // Dont care about Dep2, want to observe stuff on the Dep1 dependent object 
      var sut = CreateSut(new Mock<Dep1>().Object); 
     } 

     public void TestDep2Interaction() 
     { 
      // Dont care about Dep1, want to observe stuff on the Dep2 dependent object 
      var sut = CreateSut(new Mock<Dep2>().Object); 
     } 

     private object CreateSut() 
     { 
      return CreateSut(CreateDep1(), CreateDep2()); 
     } 

     private object CreateSut(Dep1 dep1) 
     { 
      return CreateSut(dep1, CreateDep2()); 
     } 

     private object CreateSut(Dep2 dep2) 
     { 
      return CreateSut(CreateDep1(), dep2); 
     } 

     private Sut CreateSut(Dep1 dep1, Dep2 dep2) 
     { 
      return new Sut(dep1, dep2); 
     } 

     private static Dep1 CreateDep1() 
     { 
      return new Fixture().CreateAnonymous<Dep1>(); 
     } 

     private static Dep2 CreateDep2() 
     { 
      return new Fixture().CreateAnonymous<Dep2>(); 
     } 
    } 
} 

à quelque chose comme:

public class MyAutoFixturedSpecification 
    { 
     public void TestCore() 
     { 
      // Dont care about dependencies, testing core functionality 
      var sut = CreateSut(); 
     } 

     public void TestDep1Interaction() 
     { 
      // Dont care about Dep2, want to observe stuff on the Dep1 dependent object 
      var sut = CreateSut(new Mock<Dep1>().Object); 
     } 

     public void TestDep2Interaction() 
     { 
      // Dont care about Dep1, want to observe stuff on the Dep2 dependent object 
      var sut = CreateSut(new Mock<Dep2>().Object); 
     } 

     private object CreateSut(params object[] injectedNonAnonymouses) 
     { 
      return new Fixture().Build<Sut>()./*??????*/; 
     } 
    } 

ou:

public class MyAnticipatedAutoFixturedSpecification 
    { 
     public void TestCore() 
     { 
      // Dont care about dependencies, testing core functionality 
      var sut = new Fixture().Build<Sut>().CreateAnonymous(); 
     } 

     public void TestDep1Interaction() 
     { 
      // Dont care about Dep2, want to observe stuff on the Dep1 dependent object 
      var sut = new Fixture().Build<Sut>()/*.With(new Mock<Dep1>().Object)*/.CreateAnonymous(); 
     } 

     public void TestDep2Interaction() 
     { 
      // Dont care about Dep1, want to observe stuff on the Dep2 dependent object 
      var sut = new Fixture().Build<Sut>()/*.With(new Mock<Dep2>().Object)*/.CreateAnonymous(); 
     } 
    } 

-à-dire, enlever tous les déchets de l'usine, de sorte que mes spécifications peuvent facilement faire face à une transition vers:

namespace MySutWithNewDependency 
{ 
    public class Dep1 
    { 
    } 

    public class Dep2 
    { 
    } 

    public class Dep3 
    { 
    } 

    public class Sut 
    { 
     public Sut(Dep1 dep1, Dep2 dep2, Dep3 dep3) 
     { 
     } 
    } 
} 

Bien qu'il y ait un chevauchement avec la notion d'un conteneur de automocking, je suis encore ne cherche pas un marteau en or - juste un moyen de se moquer de 0 ou 1 chose à la fois tout en étant capable de personnaliser la création d'objets dépendants sans avoir à revoir les appels explicites au constructeur Sut chaque fois que son ensemble de dépendances change.

(également en utilisant xUnit.net (style subSpec), Moq, Ninject2 (bien que veulent utiliser DI Do not dans mon cahier des charges))

Répondre

4

AutoFixture peuvent plus ou moins faire tout cela pour vous, sans tout cet échafaudage supplémentaire . Je commence avec un appareil de base et l'utiliser pour résoudre Sut:

var fixture = new Fixture(); 
var sut = fixture.CreateAnonymous<Sut>(); 

Cela suppose que Dep1 et Dep2 peuvent être créés automatiquement par AutoFixture, mais tels qu'ils sont présentés, ils peuvent, parce qu'ils ont des constructeurs par défaut.

Lorsque vous souhaitez remplacer un type spécifique, vous pouvez utiliser la méthode enregistrer comme ceci:

var fixture = new Fixture(); 

var mock = new Mock<Dep1>(); 
fixture.Register(mock.Object); 
// Setup mock if necessary... 

var sut = fixture.CreateAnonymous<Sut>(); 

Cela entraînera fixture d'utiliser mock.Object tout temps Dep1 est nécessaire, y compris lorsque le constructeur Sut est invoquée. Cela correspond parfaitement à votre exigence d'être robuste face à l'évolution des constructeurs, et l'une des principales raisons AutoFixture a été construit comme ça. Dans un scénario plus réaliste, Dep1 et Dep2 peuvent être des interfaces, auquel cas vous pouvez les enregistrer dans le cadre d'un 'Fixture par défaut'. C'est certainement aussi possible parce que dernier appel à s'inscrire gagne. Cela signifie que vous pouvez configurer une instance Fixture avec de bonnes valeurs par défaut et être toujours capable de remplacer des types spécifiques chaque fois que vous avez besoin de le faire.

Personnellement, j'utilise AutoFixture comme un conteneur auto-mocking. This discussion fournit un indice sur la façon dont cela serait possible avec l'API AutoFixture 1.1, mais le nouveau noyau pour AutoFixture 2.0 offrira de bien meilleures options d'extensibilité. Quand j'y arriverai, j'écrirai un billet sur ce sujet.

P.S. Désolé pour la réponse tardive, mais je n'ai pas vu votre question auparavant.À l'avenir, n'hésitez pas à me contacter (par exemple sur Twitter) pour une réponse plus rapide.

+0

Merci, Mark. Je vous ai considéré @ing dans un commentaire sur la question comme une manière de ping, mais merci pour l'offre! Je crois que j'ai obtenu la plupart des choses que vous avez couvertes dans la réponse (les articles de votre blog sont clairs - mais merci de consolider ma compréhension). J'ai vu 'Register 'mais je supposais que le constructeur courant me permettrait de le faire en ligne (et non que l'inscription persiste et influence de façon 'aléatoire' les utilisations suivantes de 'fixture', comme une classe de base magique ou en utilisant vos enregistrements normaux de DI dans le contexte de vos tests.J'ai un téléchargement de pourboire de deux mois alors peut-être déjà là –

+0

BTW Merci pour le TDD sans friction.La plupart des conventions normalement non écrites que vous avez là-bas sont très utiles - peut-être utile de faire un poste maître qui les indexe et leur donne une phrase résumé de chaque phrase. mettre un chapitre Ninject dans votre livre DI mais avoir un Spring.NET, je sais que V2 est sous-documenté, mais c'est là que vous entrez: P (Je suis une personne morte, j'ai encore une longue attente, même si je Je devine un ac EAP Ce serait une très bonne idée pour moi plutôt que de découvrir DI et ne pas organiquement au fil du temps) –

+0

En regardant la discussion, ça sonne comme (ou je choisis d'espérer/supposons que c'est le cas), vous serez en train de construire à la fois et une syntaxe de longue durée pour faire un hook de type 'Register' et' Resolve' qui devrait correspondre à ce modèle d'utilisation. –