调用其他方法的模拟方法仍然命中数据库。我可以避免吗?

时间:2010-06-15 19:24:20

标签: unit-testing moq

已决定使用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代码吗?

非常感谢任何建议。

1 个答案:

答案 0 :(得分:5)

就像现在一样,BizLogin是不可测试的,因为它直接实例化BizPermission,然后PermissionDal实例化BizLogin,然后命中数据库。

最佳解决方案是重构BizPermission以通过调用factory (method)Dependency Injection来替换BizPermission的直接实例化。我在你的帖子中不清楚你是否可以重构代码 - 如果是这样,这是首选的解决方案。

然而,如果重构不是一个选项,你仍然可以尝试一个讨厌的技巧。这在Java中是可能的,我不太了解C#,但由于这两种语言非常相似,我猜它也可以在C#中实现(虽然我无法填写确切的技术细节)。

您可以使用不同的模拟实现替换BizPermission的已编译类文件以进行单元测试。这当然是有风险的,因为您必须确保替代实现不会混入您的生产程序集中。此外,它需要一些混乱的类路径和东西。所以,只有在重构真的时才尝试这一点,绝对是不可能的。

如何使用测试实现替换类文件

(使用Java术语 - 我希望C#也足够清楚......)基本思想是运行时在类路径上查找类,并加载它在类路径中找到的第一个合适的类定义。因此,您可以在单元测试源文件夹中创建test-classes的模拟实现,在完全相同的包中,并使用与原始相同的接口。然后把它编译成例如一个classes文件夹(而您的生产代码编译为例如test-classes)。现在,如果您设置测试类路径以使classesBizPermission之前,运行时将在运行测试时加载假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}}的客户端中),因此它更具侵入性。

请注意,此类重构的主要目标始终是允许进行单元测试,而不是为了获得新设计之美而获奖:-)因此,最好从对现有代码的最少更改开始,这样您就可以通过测试覆盖功能。一旦你完成了测试,你就可以更自信地进行重构,以实现更清洁,更好的设计。