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.
我认为这里的问题是你最终直接依赖于SqlConnection.如果你要使用一些依赖注入的变体,这样你的代码可以访问IDbCommand而不知道它是如何构造的,你就可以毫无困难地注入你的模拟.
我知道这并没有完全回答你的问题,但从长远来看,所描述的东西会给你带来更好的可测试性.
我同意弗兰克的回答,即转向依赖注入是更好的长期解决方案,但是你可以采取一些中间步骤来推动你朝着这个方向前进,而不会把整个事情搞砸.
有一件事是将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对象.
口头禅是测试,直到恐惧在无聊中转变.我想你已经越过这条线了.如果您将控制数据读取器,那么您正在测试的唯一代码是:
device = new Device(dataReader.GetInt32(0),dataReader.GetString(1));
Run Code Online (Sandbox Code Playgroud)
这里几乎没有什么可测试的,这很好:数据层应该简单而愚蠢.因此,不要尝试对数据层进行单元测试.如果您觉得必须对其进行测试,那么请将其与真实数据库进行集成测试.
当然,将数据层隐藏在IDeviceRepository接口后面以便您可以轻松地模拟它以测试其他代码仍然是个好主意.
虽然你目前正在使用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)
| 归档时间: |
|
| 查看次数: |
5458 次 |
| 最近记录: |