unit-testing - 调用其他方法的模拟方法仍然会影响数据库。我可以避免吗?

标签 unit-testing moq

已决定使用起订量等编写一些单元测试。其中有很多遗留代码 c#

(这超出了我的控制范围,因此无法回答其原因)

现在,当您不想访问数据库但仍然间接访问数据库时,您如何应对这种情况?

这是我整理的东西,它不是真正的代码,但给了你一个想法。

遇到这种情况你会如何处理?

基本上,在模拟接口(interface)上调用方法仍然会进行 dal 调用,因为该方法内部还有其他方法不属于该接口(interface)?希望很清楚

         [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; }
            }

有什么建议吗? 我错过了显而易见的事情吗? 这是无法测试的代码吗?

非常感谢您的任何建议。

最佳答案

就目前而言,BizLogin 是不可测试的,因为它直接实例化 BizPermission,而 BizPermission 又实例化 PermissionDal,然后命中数据库。

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

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

您可以将 BizPermission 的已编译类文件替换为单元测试的不同模拟实现。这当然是有风险的,因为您必须确保替代实现不会混合到您的生产程序集中。它还需要一些困惑的类路径和东西。因此,只有在重构确实、绝对不可能的情况下才尝试这个。

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

(使用 Java 术语 - 我希望它对于 C# 来说也足够清楚...)基本思想是运行时在类路径上查找类,并加载在类路径上找到的第一个合适的类定义。因此,您可以在单元测试源文件夹中创建 BizPermission 的模拟实现,该实现位于与原始包完全相同的包中并具有相同的接口(interface)。然后将其编译成例如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 类进行最少的更改。

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

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

关于unit-testing - 调用其他方法的模拟方法仍然会影响数据库。我可以避免吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/3048330/

相关文章:

javascript - 如果在 angularJS 的配置中配置,则测试 stateProvider 状态在 $state 上返回 null

安卓 : How to write a unit test for fragment depending on a viewmodel Live data attribute?

java - 如何知道Java类是否已初始化

c# - 如何验证模拟对象是否已从模拟列表中删除?

Angular 2 - 如何覆盖另一个模块中的依赖项以进行测试

c# - 应该如何为 "send-receive"方法编写单元测试?

c# - 干净地验证传递给 Mock 的参数的许多属性 [单独在单个验证调用之外]?

c# - 使用Moq进行测试,一行使用异步可查询扩展和项目,如何测试

unit-testing - 模拟虚拟方法而不是用 Moq 模拟覆盖此方法

c# - 单元测试 - 模拟 - 可覆盖的问题 - 扩展方法