【问题标题】:Is Mocking able to replace functionality wrapped inside a method?Mocking 是否能够替换封装在方法中的功能?
【发布时间】:2009-09-02 15:31:31
【问题描述】:

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

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

    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;
    }

我假装模拟 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);
    }

我的问题是我是否可以强制方法 GetDeviceFromRepository 用模拟的方法替换 IDataReader。

【问题讨论】:

  • 这里的主要问题是我们有一些遗留代码将适合新的解决方案。一项要求是单元测试和自动化构建的覆盖率应超过 95%。我想做的是创建一些单元测试来负责实现这种覆盖,而无需执行过多的折射,也无需访问外部资源(即数据库)来快速执行此测试并每天自动构建几次。这就是采用这种方法的原因。

标签: c# unit-testing moq typemock


【解决方案1】:

尽管您当前使用的是 Moq,但我认为除非您使用 Typemock Isolator,否则您正在寻找的功能无法实现,除非您使用 Typemock Isolator(免责声明 - 我曾在 Typemock 工作)。

Isolator 有一个称为“未来对象”的功能,可以用以前创建的假对象替换对象的未来实例:

 // 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);

【讨论】:

    【解决方案2】:

    我认为这里的问题是您最终对 SqlConnection 的直接依赖。如果您使用某种依赖注入的变体,这样您的代码就可以访问 IDbCommand 而无需知道它是如何构造的,那么您将能够毫不费力地注入您的模拟。

    我知道这并不能完全回答您的问题,但从长远来看,按照描述的方式进行操作会给您带来更好的可测试性。

    【讨论】:

      【解决方案3】:

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

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

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

      然后,您可以像这样创建类的测试版本:

      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;
         }
      }
      

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

      【讨论】:

        【解决方案4】:

        口头禅是test until fear is transformed in boredom。我想你已经越过了这条线。如果您要控制数据读取器,那么您要测试的唯一代码是:

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

        这里几乎没有什么要测试的,这很好:数据层应该简单而愚蠢。所以不要尝试对你的数据层进行单元测试。如果您觉得必须对其进行测试,请针对真实数据库进行集成测试。

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

        【讨论】:

          猜你喜欢
          • 2013-05-01
          • 1970-01-01
          • 2015-04-15
          • 1970-01-01
          • 1970-01-01
          • 2015-02-22
          • 1970-01-01
          • 1970-01-01
          • 2021-01-10
          相关资源
          最近更新 更多