我对单元测试世界还不熟悉,我本周决定为现有应用添加测试覆盖率。
这是一项艰巨的任务,主要是因为要测试的课程数量,还因为编写测试对我来说都是新的。
我已经为一堆课程编写了测试,但现在我想知道我是否正确行事。
当我为一个方法编写测试时,我感觉第二次重写我已经在方法本身中编写的内容。
我的测试似乎与方法紧密相关(测试所有代码路径,期望一些内部方法被调用多次,带有某些参数),似乎如果我重构该方法,即使该方法的最终行为没有改变。
这只是一种感觉,如前所述,我没有测试经验。如果有一些经验丰富的测试人员可以就如何为现有应用程序编写出色的测试提出建议,那将非常感激。
编辑:我非常感谢Stack Overflow,我在不到15分钟的时间内得到了很多的回报,这些答案回答了我刚刚在网上阅读的更多时间。
答案 0 :(得分:165)
我的测试似乎与方法紧密相关(测试所有代码路径,期望一些内部方法被调用多次,带有某些参数),似乎如果我重构该方法,测试将失败即使方法的最终行为没有改变。
我认为你做错了。
单元测试应该:
它不应该查看方法内部以查看它正在做什么,因此更改内部不应导致测试失败。您不应该直接测试正在调用的私有方法。如果您有兴趣了解您的私人代码是否正在测试,那么请使用代码覆盖工具。但不要为此着迷:100%的覆盖范围不是必需的。
如果您的方法在其他类中调用公共方法,并且这些调用由您的接口保证,那么您可以使用模拟框架测试这些调用。
您不应该使用方法本身(或它使用的任何内部代码)动态生成预期结果。预期结果应该硬编码到您的测试用例中,以便在实现更改时不会更改。以下是单元测试应该做的简化示例:
testAdd()
{
int x = 5;
int y = -2;
int expectedResult = 3;
Calculator calculator = new Calculator();
int actualResult = calculator.Add(x, y);
Assert.AreEqual(expectedResult, actualResult);
}
请注意,不检查结果的计算方式 - 只检查结果是否正确。继续添加越来越多的简单测试用例,直到您已经涵盖尽可能多的方案为止。使用您的代码覆盖率工具来查看您是否错过了任何有趣的路径。
答案 1 :(得分:28)
对于单元测试,我发现测试驱动(测试第一,第二代码)和代码优先,第二测试非常有用。
而不是编写代码,然后编写测试。编写代码然后看看你认为代码应该做什么。考虑它的所有预期用途,然后为每个用途编写测试。我发现编写测试比编码本身更快但更复杂。测试应测试意图。还考虑了在测试编写阶段找到极端情况的意图。当然,在编写测试时,您可能会发现少数几种用法之一会导致错误(我经常发现这种错误,而且我很高兴这个错误不会破坏数据并且不会被检查)。
然而测试几乎就像编码两次。事实上,我的应用程序中存在比应用程序代码更多的测试代码(数量)。一个例子是一个非常复杂的状态机。我必须确保在添加更多逻辑之后,整个事情始终适用于所有以前的用例。由于通过查看代码非常难以理解这些情况,我最终为这台机器配备了这么好的测试套件,我相信它在进行更改后不会破坏,并且测试节省了我的屁股几次。并且当用户或测试人员发现错误的流量或角落案件下落不明时,猜猜是什么,添加到测试中并且从未再次发生过。除了让整个事物变得超级稳定之外,这确实让用户对我的工作充满信心。当出于性能原因而必须重新编写时,猜测一下,由于测试,它在所有输入上都按预期工作。
像function square(number)
这样的所有简单示例都非常棒,并且可能是花费大量时间进行测试的不良候选者。那些做重要业务逻辑的人,那就是测试很重要的地方。测试要求。不要只是测试管道。如果需求发生变化,那么猜测是什么,测试也必须。
测试不应该直接测试函数foo调用函数栏3次。那是错的。检查结果和副作用是否正确,而不是内部机制。
答案 2 :(得分:18)
值得注意的是,对现有代码进行改编的单元测试远远超过首先通过测试来创建代码的难度远。这是处理遗留应用程序的一个重大问题......如何进行单元测试?之前已经多次询问过这个问题(所以你可能作为一个骗局问题被关闭),人们通常会在这里结束:
Moving existing code to Test Driven Development
我接受了接受的答案的书籍推荐,但除此之外,答案中还有更多信息。
答案 3 :(得分:14)
不要编写测试来全面覆盖您的代码。编写保证您要求的测试。您可能会发现不必要的代码路径。相反,如果它们是必要的,它们就是满足某种要求的;找到它是什么并测试要求(而不是路径)。
让您的测试保持较小:每项要求进行一次测试。
稍后,当您需要进行更改(或编写新代码)时,请先尝试编写一个测试。只有一个。然后,您将迈出测试驱动开发的第一步。
答案 4 :(得分:13)
单元测试是关于从函数/方法/应用程序获得的输出。 结果如何产生并不重要,重要的是它是正确的。 因此,计算对内部方法的调用的方法是错误的。 我倾向于坐下来写下一个方法应该返回给定的输入值或某个特定环境,然后编写一个测试,将返回的实际值与我想出的值进行比较。
答案 5 :(得分:7)
在编写要测试的方法之前尝试编写单元测试。
这肯定会迫使你对事情的处理方式略有不同。你不知道这个方法是如何工作的,只是它应该做的事情。
您应始终测试方法的结果,而不是方法如何获得这些结果。
答案 6 :(得分:3)
测试应该可以提高可维护性。如果您更改方法并且测试中断可以是一件好事。另一方面,如果你把你的方法视为一个黑盒子,那么方法中的内容应该不重要。事实是你需要为某些测试模拟事物,在这种情况下你真的不能把这个方法当作黑盒子。你可以做的唯一事情是进行集成测试 - 你加载一个完全实例化的被测服务实例,并让它像你在应用程序中运行一样。然后你可以把它当作一个黑盒子。
When I'm writing tests for a method, I have the feeling of rewriting a second time what I
already wrote in the method itself.
My tests just seems so tightly bound to the method (testing all codepath, expecting some
inner methods to be called a number of times, with certain arguments), that it seems that
if I ever refactor the method, the tests will fail even if the final behavior of the
method did not change.
这是因为您在编写代码后编写测试。如果你反过来做(先写下测试),就不会有这种感觉。