我是Moq的新手,刚刚开始了一个已经开发的项目。我负责设置单元测试。有一个使用EnterpriseLibrary的DatabaseFactory的自定义类,如下所示:
public Database CreateCommonDatabase()
{
return CreateDatabaseInstance(string.Empty);
}
private static Database CreateDatabaseInstance(string foo)
{
var database = clientCode == string.Empty
? DatabaseFactory.CreateDatabase("COMMON")
: new OracleDatabase(new ClientConnections().GetConnectionString(foo)));
return database;
}
现在,这里使用它(ResultData是DataSet类型的另一个类):
public ResultData GetNotifications(string foo, string foo2, Database database)
{
var errMsg = string.Empty;
var retval = 0;
var ds = new DataSet();
var sqlClause =
@"[Some SELECT statement here that uses foo]";
DbCommand cm = database.GetSqlStringCommand(sqlClause);
cm.CommandType = CommandType.Text;
// Add Parameters
if (userSeq != string.Empty)
{
database.AddInParameter(cm, ":foo2", DbType.String, foo2);
}
try
{
ds = database.ExecuteDataSet(cm);
}
catch (Exception ex)
{
retval = -99;
errMsg = ex.Message;
}
return new ResultData(ds, retval, errMsg);
}
现在,最初,数据库并未作为参数传入,但该方法是使用CreateCommonDatabase方法创建DatabaseFactory的新实例,并从那里使用它。然而,这使得该类不可测试,因为我无法阻止它实际访问数据库。所以,我选择了Dependency Injection,然后传入数据库。
现在,我被卡住了,因为没有办法模拟数据库来测试GetNotifications。我想知道我是否过于复杂化,或者我是否遗漏了什么。我是以正确的方式做到这一点,还是应该重新思考如何设置这个?
编辑以添加更多信息*****
我真的不想测试数据库。我希望Data.Notifications类(上面)返回ResultData的实例,但这就是我真正想要测试的。如果我升级到业务层,我有这个:
public DataSet GetNotifications(string foo, string foo1, out int returnValue, out string errorMessage, Database database)
{
ResultData rd = new data.Notifications().GetNotifications(foo, foo1, database);
returnValue = rd.ResultValue;
errorMessage = rd.ErrorMessage;
return rd.DataReturned;
}
所以,最初,数据库没有被传入,它是创建它的Data.Notifications类 - 但是再次,如果我这样离开它,我忍不住打了数据库来测试这个业务图层对象。我修改了所有代码以传递数据库(它创建了一个Web的Base页面),但现在我不确定下一步该做什么。我认为我是一个单元测试,而不是解决这个问题,但显然,要么我错了,要么我在正确的道路上遇到了精神障碍。
答案 0 :(得分:2)
如果其中的方法是虚拟的,您应该能够创建模拟数据库对象。如果他们不是,那么你有一点问题。
我不知道“数据库”是什么类型,但你有几个选择。
如果您拥有数据库的源代码,我建议您提取接口IDatabase,而不是处理Database类类型。这将消除一些复杂性,并为您提供极其可测试的东西。
如果您无法访问Database类,则可以使用另一个抽象层来解决此问题。在这种情况下,许多人使用包装数据访问层的存储库模式。一般来说,在这种情况下,大多数人将测试Respository类留给集成测试(没有任何隔离的测试),而不是单元测试。
以下是使用选项#1设置测试的方法:
[TestMethod]
public void GetNotifications_PassedNullFoo_ReturnsData()
{
//Arrange
Mock<IDatabase> mockDB = new Mock<IDatabase>();
mockDB.Setup(db => db.ExecuteDataSet()).Returns(new DataSet() ... );
//Act
FooClass target = new fooClass();
var result = target.GetNotifications(null, "Foo2", mockDB.Object);
//Assert
Assert.IsTrue(result.DataSet.Rows.Count > 0);
}
我的数据集代码有点生疏,但希望这能为您提供一般的想法。
答案 1 :(得分:1)
根据您提供的代码,我认为您希望与数据库通信,而不是模拟版本。
原因是您的GetNotifications代码包含特定于数据库的指令,并且您希望这些指令在数据库引擎级别通过验证。因此,只需传入连接到测试数据库实例的数据库。
如果您将测试抽象提升到更高级别,您为数据库调用构建了单元测试,并且使用了模拟数据库的此测试版本,那么您仍然需要运行集成测试,最终会达到三倍相同数量的代码覆盖率的工作。在我看来,在您控制的层边界进行集成测试然后为合同和集成测试的双方编写单元测试效率要高得多。