您正在开始单元测试,直接解决一些重要的问题。
问题 1:如何处理实现细节/私有函数? 答:单元测试是关于发现代码中的错误,这是单元测试的一个主要目标(以及大多数其他各种测试)。另一个主要目标是通过在软件更改时充当回归测试来防止引入错误。错误存在于实现中 - 不同的实现带有不同的错误。因此,一定要测试实现细节。此处支持的一个重要工具是覆盖率分析,它向您显示您的测试已经到达了实现代码的哪些部分。
您甚至可以测试函数合同之外的方面:a) 负测试是有意检查无效/未指定输入行为的测试,对于确保系统安全非常重要。因为,即使提供了无效输入,系统也不应该因为例如读取或写入越界内存而被黑客入侵。但是,这可能不适用于您的示例,因为您的方法很可能被指定为实现“总函数”而不是“部分函数”。 b) 实现细节的测试(如果可访问)甚至可以在当前实现所需的范围之外进行。这样做可以防止即将对组件进行的更改(例如 API 扩展)中出现错误。
不过,单元测试也有次要目标。其中之一是避免您的测试在实现细节发生变化时不必要地中断。实现次要目标的一种方法是通过公共 API 测试实现细节。这样,对实现细节的某些重新设计不会破坏您的测试:私有函数的重命名、拆分或合并不会影响测试。然而,切换到不同的算法可能需要您重新考虑您的测试:斐波那契函数的迭代/递归实现的测试看起来与使用 Moivre/Binet 的封闭形式表达式的实现不同,或者用于查找表实现。
对于您的示例,这意味着您应该尝试通过公共 API 测试私有函数的功能。
问题 2:如何处理与软件其他部分的依赖关系? 单元测试的重点是发现小的、孤立的代码片段中的错误。当这些代码片段依赖于其他代码部分时,这会对您正确进行单元测试的能力产生负面影响。但是否真的如此取决于实际的依赖关系。例如,如果您的代码使用Math.sin() 函数,这也是对不同代码部分的依赖,但这种依赖通常不会损害您正确测试代码的能力。
在以下情况下,对其他组件的依赖会困扰您: 使用其他组件使得在您的测试代码中激发所有有趣的场景变得困难。或者,使用其他组件会导致不确定的行为(时间、随机性……)。或者,使用其他组件会导致构建或执行时间过长。或者,其他组件有问题,甚至还不可用。
如果不满足所有这些条件(Math.sin() 函数通常就是这种情况),您通常可以将其他组件作为测试的一部分。但是,您应该记住,在您的单元测试中,您仍然专注于代码中的错误,而不是开始编写实际测试其他组件的测试:请记住,其他组件有自己的测试。
在您的示例中,您选择了Outerclass 来拥有一些看似微不足道的功能。在这种情况下,您可以使用 Outerclass 仅保留部分测试。然而,这只是你身边的一个例子——根据上述标准,真正的其他班级实际上可能会令人不安。如果是这种情况,那么您将不得不以某种方式管理这种依赖关系,而这一切都需要以某种方式实现对测试友好的设计。
这里有一整套方法,因此您最好在网络上搜索“可测试性设计”和“控制反转”。而且,您还应该尝试了解单元测试和集成测试的区别:这将帮助您避免尝试将单元测试应用于应该通过集成测试进行测试的代码部分。