web-dev-qa-db-fra.com

La moquerie introduit la manipulation dans le code de production

En supposant une interface IReader, une implémentation de l'interface IReader ReaderImplementation et une classe ReaderConsumer qui consomme et traite les données du lecteur.

public interface IReader
{
     object Read()
}

La mise en oeuvre

public class ReaderImplementation
{
    ...
    public object Read()
    {
        ...
    }
}

Consommateur:

public class ReaderConsumer()
{
    public string location

    // constructor
    public ReaderConsumer()
    {
        ...
    }

    // read some data
    public object ReadData()
    {
        IReader reader = new ReaderImplementation(this.location)
        data = reader.Read()
        ...
        return processedData    
    }
}

Pour tester ReaderConsumer et le traitement, j'utilise une maquette d'IReader. ReaderConsumer devient donc:

public class ReaderConsumer()
{
    private IReader reader = null

    public string location

    // constructor
    public ReaderConsumer()
    {
        ...
    }

    // mock constructor
    public ReaderConsumer(IReader reader)
    {
        this.reader = reader
    }

    // read some data
    public object ReadData()
    {
        try
        {
            if(this.reader == null)
            {
                 this.reader = new ReaderImplementation(this.location)
            }

            data = reader.Read()
            ...
            return processedData    
        }
        finally
        {
            this.reader = null
        }
    }
}

Dans cette solution, le mocking introduit une phrase if pour le code de production car seul le constructeur mocking fournit des instances de l'interface.

Lors de l'écriture, je me rends compte que le bloc try-finally est quelque peu indépendant car il est là pour gérer l'utilisateur qui change l'emplacement pendant l'exécution de l'application.

Dans l'ensemble, il sent mal, comment pourrait-il être mieux géré?

15
kristian mo

Au lieu d'initialiser le lecteur à partir de votre méthode, déplacez cette ligne

{
    this.reader = new ReaderImplementation(this.location)
}

Dans le constructeur sans paramètre par défaut.

public ReaderConsumer()
{
    this.reader = new ReaderImplementation(this.location)
}

public ReaderConsumer(IReader reader)
{
    this.reader = reader
}

Il n'y a pas de "constructeur factice", si votre classe a une dépendance nécessite pour fonctionner, alors le constructeur devrait soit fournir cette chose, soit la créer.

67
RubberDuck

Vous n'avez besoin que d'un seul constructeur:

public class ReaderConsumer()
{
    private IReader reader = null

    public string location

    // constructor
    public ReaderConsumer(IReader reader)
    {
        this.reader = reader;
    }

dans votre code de production:

var rc = new ReaderConsumer(new ReaderImplementation(0));

dans votre test:

var rc = new ReaderConsumer(new MockImplementation(0));
54
Ewan

Examiner l'injection de dépendance et l'inversion du contrôle

Ewan et RubberDuck ont ​​tous deux d'excellentes réponses. Mais je voulais mentionner un autre domaine à examiner, à savoir l'injection de dépendance (DI) et l'inversion de contrôle (IoC). Ces deux approches déplacent le problème que vous rencontrez dans un framework/bibliothèque afin que vous n'ayez pas à vous en soucier.

Votre exemple est simple et est rapidement supprimé, mais, inévitablement, vous allez vous en inspirer et vous vous retrouverez avec des tonnes de constructeurs ou des routines d'initialisation qui ressemblent à:

var foo = new Foo (new Bar (new Baz (), new Quz ()), new Foo2 ());

Avec DI/IoC, vous utilisez une bibliothèque qui vous permet d'énoncer les règles pour faire correspondre les interfaces aux implémentations, puis vous dites simplement "Donnez-moi un Foo" et cela fonctionne comment câbler le tout.

Il y a beaucoup de conteneurs IoC très sympathiques (comme on les appelle), et je vais recommander un à regarder, mais, veuillez explorer, car il y a beaucoup de bons choix.

Un simple pour commencer est:

http://www.ninject.org/

Voici une liste à explorer:

http://www.hanselman.com/blog/ListOfNETDependencyInjectionContainersIOC.aspx

13
Reginald Blue