Question

Je suis en train de définir un moyen de simuler un cas sur l'accès à une base de données sans accès ... C'est semble probablement tout à fait fou, mais ce n'est pas.

Voici un exemple d'une méthode que je voudrais tester:

    public IDevice GetDeviceFromRepository(string name)
    {
        IDevice device = null;
        IDbConnection connection = new SqlConnection(ConnectionString);
        connection.Open();
        try
        {
            IDbCommand command = connection.CreateCommand();
            command.CommandText = string.Format("SELECT DEVICE_ID,DEVICE_NAME FROM DEVICE WHERE DEVICE_NAME='{0}'", name);
            IDataReader dataReader = command.ExecuteReader();
            if(dataReader.NextResult())
            {
                device = new Device(dataReader.GetInt32(0),dataReader.GetString(1));
            }
        }
        finally
        {
            connection.Close();
        }
        return device;
    }

Je fais semblant de se moquer IDataReader donc je peux contrôler ce qui est lu. Quelque chose comme ça (en utilisant le framework QMC):

    [TestMethod()]
    public void GetDeviceFromRepositoryTest()
    {
        Mock<IDataReader> dataReaderMock = new Mock<IDataReader>();
        dataReaderMock.Setup(x => x.NextResult()).Returns(true);
        dataReaderMock.Setup(x => x.GetInt32(0)).Returns(000);
        dataReaderMock.Setup(x => x.GetString(1)).Returns("myName");
        Mock<IDbCommand> commandMock = new Mock<IDbCommand>();
        commandMock.Setup(x => x.ExecuteReader()).Returns(dataReaderMock.Object);
        Mock<RemoveDeviceManager> removeMock = new Mock<RemoveDeviceManager>();
        removeMock.Setup()
        RemoveDeviceManager target =new RemoveDeviceManager(new Device(000, "myName")); 
        string name = string.Empty; 
        IDevice expected = new Device(000, "myName"); // TODO: Initialize to an appropriate value
        IDevice actual;
        actual = target.GetDeviceFromRepository(name);
        Assert.AreEqual(expected.SerialNumber, actual.SerialNumber);
        Assert.AreEqual(expected.Name, actual.Name);
    }

Ma question est de savoir si je peux forcer la méthode GetDeviceFromRepository pour remplacer IDataReader par mocqué.

Était-ce utile?

La solution

Bien que vous utilisez actuellement Moq Je pense que la fonctionnalité que vous cherchez ne peut pas être achived sans injection de dépendance, sauf si vous utilisez Typemock Isolateur ( Disclaimer - Je travaillais à Typemock ).

Isolateur a une fonction appelée « objets futurs » qui permettent de remplacer un avenir instanciation d'un objet avec un objet faux précédemment créé:

 // Create fake (stub/mock whateever) objects
 var fakeSqlConnection = Isolate.Fake.Instance<SqlConnection>();
 var fakeCommand = Isolate.Fake.Instance<SqlCommand>();
 Isolate.WhenCalled(() => fakeSqlConnection.CreateCommand()).WillReturn(fakeCommand);

 var fakeReader = Isolate.Fake.Instance<SqlDataReader>();
 Isolate.WhenCalled(() => fakeCommand.ExecuteReader()).WillReturn(fakeReader);

 // Next time SQLConnection is instantiated replace with our fake
 Isolate.Swap.NextInstance<SqlConnection>().With(fakeSqlConnection);

Autres conseils

Je pense que le problème ici est votre dépendance directe en fin de compte à SqlConnection . Si vous utilisez une variante d'injection de dépendance telle que votre code a accès à IDbCommand sans savoir comment il se construit, vous seriez en mesure d'injecter votre maquette sans beaucoup de tracas.

Je comprends cela ne veut pas tout à fait répondre à votre question, mais à long terme faire des choses comme décrit vous donner beaucoup mieux testabilité.

Je suis d'accord avec la réponse de Frank que le déplacement vers l'injection de dépendance est la meilleure solution à long terme, mais il y a des étapes intermédiaires que vous pouvez prendre pour vous déplacer dans cette direction sans mordre la chose.

Une chose est de déplacer la construction de la classe IDbConnection dans une méthode virtuelle protégée à l'intérieur de votre classe:

protected virtual IDbConnection CreateConnection()
{
    return new SqlConnection(ConnectionString);
}

Ensuite, vous pouvez créer une version de test de votre classe comme ceci:

public class TestingRemoteDeviceManager : RemoteDeviceManager
{
   public override IDbConnection CreateConnection()
   {
         IDbConnection conn = new Mock<IDbConnection>();
         //mock out the rest of the interface, as well as the IDbCommand and
         //IDataReader interfaces
         return conn;
   }
}

qui retourne un IDbConnection Mock ou faux au lieu du béton SqlConnection. Ce faux peut alors retourner un faux objet IDbCommand, qui peut alors renvoyer un objet IDataReader faux.

Le mantra est Test jusqu'à ce que la peur se transforme en ennui . Je pense que vous avez franchi cette ligne ici. Si vous prendre le contrôle du lecteur de données, alors vous testez le seul code est le suivant:

device = new Device(dataReader.GetInt32(0),dataReader.GetString(1));

Il n'y a presque rien à tester ici, ce qui est bon: la couche de données doit être simple et stupide. Donc, ne pas essayer de tester votre unité couche de données. Si vous sentez que vous devez le tester, puis l'intégration test contre une véritable base de données.

Bien sûr, cacher votre couche de données derrière une interface IDeviceRepository de sorte que vous pouvez facilement moquer afin de tester autre Code est toujours une bonne idée.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top