在讽刺能够取代包裹的方法中的功能?
-
21-09-2019 - |
题
我试图定义一个方法可以对接入到数据库中,而无需访问模拟情况下......这可能听起来很疯狂,但事实并非如此。
下面是一个关于一个方法的示例我想测试:
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);
}
我的问题是i是否可以强制方法GetDeviceFromRepository通过嘲笑一个替换的IDataReader。
解决方案
虽然你目前使用的起订量我认为你需要的功能,不能没有依赖注入被achived除非你使用Typemock隔离器(免责声明 - 我曾在Typemock 的)。
隔离器有一个称为“未来的对象”,使用先前创建的假对象替换的对象的未来实例的功能:
// 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);
其他提示
我觉得这里的问题是你的直接依赖最终的的SqlConnection 的。如果你想使用依赖注入的一些变体,例如,你的代码获取访问IDbCommand的不知道它如何被构建的,你就可以不用麻烦了以注入模拟。
我明白这并不完全回答你的问题,但是从长远来看,这样做的东西,描述会给你更好的可测试性。
我同意弗兰克的回答朝着依赖注入移动是更好的长期解决方案,但也有可以带给您移动在这个方向没有断咬了整个事情的一些中间步骤。
的一件事是对的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;
}
}
,它返回一个模拟或假的IDbConnection代替混凝土的SqlConnection。然后,该假可以返回一个假IDbCommand的对象,然后可以返回一个假IDataReader的对象。
在曼陀罗是 测试直到恐惧是无聊转化 。我想你已经越过这条线在这里。如果你将采取的数据读取器的控制,那么你正在测试的唯一的代码是这样的:
device = new Device(dataReader.GetInt32(0),dataReader.GetString(1));
有是几乎没有在这里测试,这是很好的:数据层应该是简单的和笨。所以,不要试图进行单元测试你的数据层。如果你觉得你必须对其进行测试,然后集成测试其对实际的数据库。
当然,隐藏你的数据层IDeviceRepository
接口后面,这样就可以轻松地嘲笑它,以便测试的其他的代码仍然是一个不错的主意。