【问题标题】:Unit Test public method that calls private method and other objects in Java单元测试调用私有方法的公共方法和Java中的其他对象
【发布时间】:2019-07-24 01:43:33
【问题描述】:

我是单元测试的新手,最近尝试了 JUnit 测试和 Mockito。

我正在尝试对调用多个私有方法并创建其他类的私有对象的方法进行单元测试。

如何对方法进行单元测试。

例如,如果我有以下代码:

class ClassToTest {
    private OuterClass outerClass;
    public Boolean function() {
        outerClass = new OuterClass(20);
        return innerFunction(outerClass.getValue());
    }

    private Boolean innerFunction(int val) {
        if (val % 2 == 0)
            return true;
        return false;
    }
}

我很困惑如何测试公共功能。

【问题讨论】:

    标签: java unit-testing junit mockito


    【解决方案1】:

    方法如何实现并不重要;该方法应该有一个它遵守的合同,并且 that 是您通过测试验证的内容。在这个例子中,如果outerClass 的值是偶数,function() 应该返回true。实现此目的的一种方法是注入(传递到ClassToTest 构造函数)outerClass 的实例,以便您可以在测试时控制该值:

    @Test
    public void trueWhenEven() {
        var outer = new OuterClass(2);
        var ctt = new ClassToTest(outer);
        assertTrue(ctt.function());
    }
    

    有时契约只是一个方法调用其他一些对象的方法;在这种情况下,您可以使用 Mockito 或类似的库来验证交互。

    【讨论】:

      【解决方案2】:

      通常使用 Mockito 这将需要使用依赖注入,然后您将注入 OuterClass 的模拟进行测试。

      如果您真的想在不添加 Spring 类型框架的情况下进行测试,我可以考虑 3 个选项:

      1) 将此作为集成测试并测试所有内容的真实实例

      2) 更改您的代码,以便通过从 setter 或构造函数传入的 Object 创建 OuterClass,然后为您的测试传入一个模拟

      3) 将private OuterClass outerClass; 更改为protected OuterClass outerClass; 并确保您的测试包结构与您的实际代码包结构相同,然后您可以在测试设置中执行outerClass = Mockito.mock(OuterClass);

      【讨论】:

      • 想太多了。这里真正的重点是总体上提出一个更好的界面。 Spring能解决这个问题吗?不是一个好的提示。
      【解决方案3】:

      您正在开始单元测试,直接解决一些重要的问题。

      问题 1:如何处理实现细节/私有函数? 答:单元测试是关于发现代码中的错误,这是单元测试的一个主要目标(以及大多数其他各种测试)。另一个主要目标是通过在软件更改时充当回归测试来防止引入错误。错误存在于实现中 - 不同的实现带有不同的错误。因此,一定要测试实现细节。此处支持的一个重要工具是覆盖率分析,它向您显示您的测试已经到达了实现代码的哪些部分。

      您甚至可以测试函数合同之外的方面:a) 负测试是有意检查无效/未指定输入行为的测试,对于确保系统安全非常重要。因为,即使提供了无效输入,系统也不应该因为例如读取或写入越界内存而被黑客入侵。但是,这可能不适用于您的示例,因为您的方法很可能被指定为实现“总函数”而不是“部分函数”。 b) 实现细节的测试(如果可访问)甚至可以在当前实现所需的范围之外进行。这样做可以防止即将对组件进行的更改(例如 API 扩展)中出现错误。

      不过,单元测试也有次要目标。其中之一是避免您的测试在实现细节发生变化时不必要地中断。实现次要目标的一种方法是通过公共 API 测试实现细节。这样,对实现细节的某些重新设计不会破坏您的测试:私有函数的重命名、拆分或合并不会影响测试。然而,切换到不同的算法可能需要您重新考虑您的测试:斐波那契函数的迭代/递归实现的测试看起来与使用 Moivre/Binet 的封闭形式表达式的实现不同,或者用于查找表实现。

      对于您的示例,这意味着您应该尝试通过公共 API 测试私有函数的功能。

      问题 2:如何处理与软件其他部分的依赖关系? 单元测试的重点是发现小的、孤立的代码片段中的错误。当这些代码片段依赖于其他代码部分时,这会对您正确进行单元测试的能力产生负面影响。但是否真的如此取决于实际的依赖关系。例如,如果您的代码使用Math.sin() 函数,这也是对不同代码部分的依赖,但这种依赖通常不会损害您正确测试代码的能力。

      在以下情况下,对其他组件的依赖会困扰您: 使用其他组件使得在您的测试代码中激发所有有趣的场景变得困难。或者,使用其他组件会导致不确定的行为(时间、随机性……)。或者,使用其他组件会导致构建或执行时间过长。或者,其他组件有问题,甚至还不可用。

      如果不满足所有这些条件(Math.sin() 函数通常就是这种情况),您通常可以将其他组件作为测试的一部分。但是,您应该记住,在您的单元测试中,您仍然专注于代码中的错误,而不是开始编写实际测试其他组件的测试:请记住,其他组件有自己的测试。

      在您的示例中,您选择了Outerclass 来拥有一些看似微不足道的功能。在这种情况下,您可以使用 Outerclass 仅保留部分测试。然而,这只是你身边的一个例子——根据上述标准,真正的其他班级实际上可能会令人不安。如果是这种情况,那么您将不得不以某种方式管理这种依赖关系,而这一切都需要以某种方式实现对测试友好的设计。

      这里有一整套方法,因此您最好在网络上搜索“可测试性设计”和“控制反转”。而且,您还应该尝试了解单元测试和集成测试的区别:这将帮助您避免尝试将单元测试应用于应该通过集成测试进行测试的代码部分。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-09-20
        • 2023-03-19
        • 2012-05-14
        相关资源
        最近更新 更多