Mocking能否替换方法中包含的功能?

4 c# unit-testing moq typemock

我正在尝试定义一种方法来模拟访问数据库而不访问的情况......这可能听起来很疯狂,但事实并非如此.

这是一个关于我想测试的方法的示例:

    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;
    }
Run Code Online (Sandbox Code Playgroud)

我假装模仿IDataReader,所以我可以控制正在阅读的内容.类似的东西(使用Moq框架):

    [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);
    }
Run Code Online (Sandbox Code Playgroud)

我的问题是我是否可以通过模拟方法强制方法GetDeviceFromRepository来替换IDataReader.

flq*_*flq 5

我认为这里的问题是你最终直接依赖于SqlConnection.如果你要使用一些依赖注入的变体,这样你的代码可以访问IDbCommand而不知道它是如何构造的,你就可以毫无困难地注入你的模拟.

我知道这并没有完全回答你的问题,但从长远来看,所描述的东西会给你带来更好的可测试性.


Cra*_*eer 5

我同意弗兰克的回答,即转向依赖注入是更好的长期解决方案,但是你可以采取一些中间步骤来推动你朝着这个方向前进,而不会把整个事情搞砸.

有一件事是将IDbConnection类的构造移动到类中受保护的虚方法中:

protected virtual IDbConnection CreateConnection()
{
    return new SqlConnection(ConnectionString);
}
Run Code Online (Sandbox Code Playgroud)

然后,您可以创建类的测试版本,如下所示:

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;
   }
}
Run Code Online (Sandbox Code Playgroud)

返回Mock或伪IDbConnection而不是具体的SqlConnection.那假货可以返回一个假的IDbCommand对象,然后可以返回一个假的IDataReader对象.


Wim*_*nen 5

口头禅是测试,直到恐惧在无聊中转变.我想你已经越过这条线了.如果您将控制数据读取器,那么您正在测试的唯一代码是:

device = new Device(dataReader.GetInt32(0),dataReader.GetString(1));
Run Code Online (Sandbox Code Playgroud)

这里几乎没有什么可测试的,这很好:数据层应该简单而愚蠢.因此,不要尝试对数据层进行单元测试.如果您觉得必须对其进行测试,那么请将其与真实数据库进行集成测试.

当然,将数据层隐藏在IDeviceRepository接口后面以便您可以轻松地模拟它以测试其他代码仍然是个好主意.


Dro*_*per 5

虽然你目前正在使用Moq,但我认为除非你使用Typemock Isolator(免责声明 - 我在Typemock工作),否则无需依赖注入就无法获得你正在寻找的功能.

Isolator有一个名为"future objects"的功能,可以用以前创建的假对象替换对象的未来实例化:

 // 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);
Run Code Online (Sandbox Code Playgroud)