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

use*_*969 2 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; }
            }
Run Code Online (Sandbox Code Playgroud)

有什么建议?我错过了明显的吗?这是Untestable代码吗?

非常感谢任何建议.

Pét*_*rök 5

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

最好的解决方案是重构BizLoginBizPermission通过调用工厂(方法)依赖注入来替换直接实例化.从你的帖子中我不清楚你是否可以重构代码 - 如果是这样,这是首选的解决方案.

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

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

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

(使用Java术语 - 我希望C#也足够清楚......)基本思想是运行时正在类路径上查找类,并且它会加载它在类路径中找到的第一个合适的类定义.因此,您可以BizPermission在单元测试源文件夹中创建一个模拟实现,在完全相同的包中,并使用与原始相同的接口.然后把它编译成例如一个test-classes文件夹(而你的生产代码被编译成例如classes).现在,如果您将测试类路径设置test-classes为之前classes,运行时将BizPermissionBizLogin尝试实例化此类时运行测试而不是原始测试时加载假类.

重构使用工厂方法的示例

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

在测试代​​码中:

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

这样,您可以通过BizLoginForTest对原始BizLogin类的最小更改来测试关键功能.

另一种方法是注入一个完整的工厂对象,如Jeff的评论中所述.这将需要更多的代码更改(可能包括在客户端中BizLogin),因此它更具侵入性.

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