Ice*_*dor 5 java junit unit-testing junit4
在用 Java 编写的中型开源项目中,我们收到许多错误报告,但针对这些错误报告的单元测试较少,并且修复这些错误的补丁也较少。
当提供单元测试但未提供补丁时,我们通过将测试功能添加到套件来验证主干是否存在错误。我们提交它,但junit4 @Ignore为其添加注释,以免因已知错误而破坏构建。
某些错误可能是相关的,或者对一个错误所做的更改可能会修复另一个错误。我们想知道以前已知失败的测试用例何时不再失败,以便我们可以通知观察该错误的人员并关闭该错误。
假设我们有一些有缺陷的函数——引发异常、产生意外的副作用或返回错误的值。(现实世界的例子)
public void buggyFunction() {
throw new UnsupportedOperationException("this will be implemented later");
}
Run Code Online (Sandbox Code Playgroud)
和一些单元测试来测试buggyFunction
@Test
public void knownIssue() {
buggyFunction();
}
Run Code Online (Sandbox Code Playgroud)
选项 1:@Ignore测试
@Ignore("this test is currently failing. see bug 12345")
@Test
public void knownIssue() {
buggyFunction();
}
Run Code Online (Sandbox Code Playgroud)
选项 2 :标准的 junit4 方式是在整个测试函数中使用@Test(expected=MyException.class)或散布s。@Rule ExpectedException既不向用户提供有用的消息,说明为什么失败的测试意味着错误已被修复,也没有更新单元测试并关闭错误。此外,如果抛出预期的异常,则测试通过,但在这种情况下测试毫无意义。最好是失败(当错误已修复时)或跳过(当错误仍然存在时)。
// when this bug is fixed, it should not throw an exception
// TODO: delete expected=UnsupportedOperationException.class
@Test(expected=UnsupportedOperationException.class)
public void knownIssue() {
buggyFunction();
}
Run Code Online (Sandbox Code Playgroud)
或者
@Rule
public final ExpectedException thrown = ExpectedException.none();
@Test
public void knownIssue() {
thrown.expect(UnsupportedOperationException.class);
thrown.expectMessage("this will be implemented later");
buggyFunction();
thrown.expect(ExpectedException.none());
}
Run Code Online (Sandbox Code Playgroud)
选项 3:使用样板脚手架进行 Booger up 测试
@Test
public void knownIssue() {
try {
buggyFunction();
} catch (UnsupportedOperationException e) {
// we know that buggyFunction is broken, so skip this test
assumeTrue("Skipping test. Expected exception: " + e, false);
}
// surprise! buggyFunction isn't broken anymore!
fail("The function is no longer buggy! " +
"Update the unit test and close bug 12345!");
}
Run Code Online (Sandbox Code Playgroud)
有没有更好的替代方案:
我可以在 Python 中轻松完成类似的事情,其中未计算的函数是第一类对象。在 Java 6 中也可以完成同样的操作(是的,这就是我们使用的版本),但可能需要比选项 3 更多的样板文件。请告诉我我错了。
def alertWhenFixed(expected=Exception, bug=12345):
def decorator(func):
def func_wrapper(*args, **kwargs):
try:
func(*args, **kwargs)
except Exception as e:
if isinstance(e, expected):
assumeTrue("Skipping test. Expected exception: {}"
.format(e), false)
else:
raise e
fail("The function is no longer buggy! " +
"Update the unit test and close bug {}".format(bug))
return func_wrapper
return decorator
@alertWhenFixed(expected=UnsupportedOperationException, bug=12345)
def knownIssue():
buggyFunctionThrowsException()
@alertWhenFixed(expected=AssertionFailed)
def knownIssue():
assertEquals(42, buggyFunctionReturnsWrongValue())
@alertWhenFixed(expected=AssertionFailed)
def knownIssue():
buggyFunctionHasWrongSideEffect()
assertEquals(42, getSideEffect())
Run Code Online (Sandbox Code Playgroud)
该装饰器可以测试引发异常、返回错误值或产生错误副作用的已知问题。
这个装饰器是 100% 可重用的,所以没有复制粘贴的 try/ except 脚手架,当已知问题得到修复时我可以删除一行代码,最重要的是我可以保留测试用例逻辑。
知道这是否可以翻译到 Java 6 或 7 吗?
除了您概述的技术之外,我没有看到其他技术选择。
我认为解决方案来自不同的角度:您的工具没有为开发人员提供他们所需的反馈。你看,重点是:当开发人员决定修复buggyFunction ()时,就需要以严格组织的方式进行。意义:
开发人员想要修复一个错误。因此,他应该充分意识到该错误以及与此任务相关的所有工作:
换句话说:您希望您的开发人员收到快速反馈。他应该能够更改buggyFunction,然后,几分钟后,他应该收到关于现在失败的测试用例的通知。
因此,即使他无意中修复了错误,关键的一点是他会被告知他的更改破坏了其他地方的测试。然后,其他测试失败的原因就是他的责任。如果您的系统不以有效的方式支持该工作流程,那么更改单元测试根本没有帮助。因为你的单元测试并不是这里真正的问题。
从这个角度来看,唯一明智的选择是在选项 2 和选项 3 之间。因为它们为您提供了系统的这一部分可以为您提供的“最佳”功能:有人更改了代码,然后测试失败了。并尽快通知更改代码的人;然后弄清楚发生了什么。从那时起,它只是关于该测试的质量(指出测试失败时意味着什么);以及相关人员的技能(然后做正确的事情)。