已决定使用moq等编写一些单元测试。这是很多遗留代码c#
(这是我无法控制的,所以不能回答这个问题的原因)
现在,如果您不想访问数据库,但间接仍然访问数据库,您如何应对这种情况?
这是我把它放在一起的东西,它不是真正的代码,而是给你一个想法。
你会如何处理这种情况?
基本上在模拟接口上调用一个方法仍然会调用dal调用,因为在该方法中有其他方法不属于该接口的一部分?希望它很清楚
[TestFixture]
public class Can_Test_this_legacy_code
{
[Test]
public void Should_be_able_to_mock_login()
{
var mock = new Mock<ILoginDal>();
User user;
var userName = "Jo";
var password = "password";
mock.Setup(x => x.login(It.IsAny<string>(), It.IsAny<string>(),out user));
var bizLogin = new BizLogin(mock.Object);
bizLogin.Login(userName, password, out user);
}
}
public class BizLogin
{
private readonly ILoginDal _login;
public BizLogin(ILoginDal login)
{
_login = login;
}
public void Login(string userName, string password, out User user)
{
//Even if I dont want to this will call the DAL!!!!!
var bizPermission = new BizPermission();
var permissionList = bizPermission.GetPermissions(userName);
//Method I am actually testing
_login.login(userName,password,out user);
}
}
public class BizPermission
{
public List<Permission>GetPermissions(string userName)
{
var dal=new PermissionDal();
var permissionlist= dal.GetPermissions(userName);
return permissionlist;
}
}
public class PermissionDal
{
public List<Permission> GetPermissions(string userName)
{
//I SHOULD NOT BE GETTING HERE!!!!!!
return new List<Permission>();
}
}
public interface ILoginDal
{
void login(string userName, string password,out User user);
}
public interface IOtherStuffDal
{
List<Permission> GetPermissions();
}
public class Permission
{
public int Id { get; set; }
public string Name { get; set; }
}
有什么建议吗? 我错过了明显的吗? 这是Untestable代码吗?
非常感谢任何建议。
答案 0 :(得分:5)
就像现在一样,BizLogin
是不可测试的,因为它直接实例化BizPermission
,然后PermissionDal
实例化BizLogin
,然后命中数据库。
最佳解决方案是重构BizPermission
以通过调用factory (method)或Dependency Injection来替换BizPermission
的直接实例化。我在你的帖子中不清楚你是否可以重构代码 - 如果是这样,这是首选的解决方案。
然而,如果重构不是一个选项,你仍然可以尝试一个讨厌的技巧。这在Java中是可能的,我不太了解C#,但由于这两种语言非常相似,我猜它也可以在C#中实现(虽然我无法填写确切的技术细节)。
您可以使用不同的模拟实现替换BizPermission
的已编译类文件以进行单元测试。这当然是有风险的,因为您必须确保替代实现不会混入您的生产程序集中。此外,它需要一些混乱的类路径和东西。所以,只有在重构真的时才尝试这一点,绝对是不可能的。
(使用Java术语 - 我希望C#也足够清楚......)基本思想是运行时在类路径上查找类,并加载它在类路径中找到的第一个合适的类定义。因此,您可以在单元测试源文件夹中创建test-classes
的模拟实现,在完全相同的包中,并使用与原始相同的接口。然后把它编译成例如一个classes
文件夹(而您的生产代码编译为例如test-classes
)。现在,如果您设置测试类路径以使classes
在BizPermission
之前,运行时将在运行测试时加载假BizLogin
类,而不是原始public class BizLogin
{
private readonly ILoginDal _login;
public BizLogin(ILoginDal login)
{
_login = login;
}
protected BizPermission getBizPermission()
{
return new BizPermission();
}
public void Login(string userName, string password, out User user)
{
var bizPermission = getBizPermission();
var permissionList = bizPermission.GetPermissions(userName);
//Method I am actually testing
_login.login(userName,password,out user);
}
}
尝试实例化这个类。
public class FakeBizPermission implements BizPermission
{
public List<Permission>GetPermissions(string userName)
{
// produce and return fake permission list
}
}
public class BizLoginForTest
{
public BizLoginForTest(ILoginDal login)
{
super(login);
}
protected BizPermission getBizPermission()
{
return new FakeBizPermission();
}
}
在测试代码中:
BizLoginForTest
通过这种方式,您可以通过BizLogin
测试关键功能,只需对原始BizLogin
类进行最少的更改。
另一种方法是注入一个完整的工厂对象,如Jeff的评论中所述。这将需要更多的代码更改(可能包括在{{1}}的客户端中),因此它更具侵入性。
请注意,此类重构的主要目标始终是允许进行单元测试,而不是为了获得新设计之美而获奖:-)因此,最好从对现有代码的最少更改开始,这样您就可以通过测试覆盖功能。一旦你完成了测试,你就可以更自信地进行重构,以实现更清洁,更好的设计。