知道@ Ignore'd测试何时通过

时间:2016-09-21 06:34:45

标签: java unit-testing junit junit4

在用Java编写的大中型开源项目中,我们会收到许多错误报告,这些错误报告的单元测试次数减少,以及关闭这些错误的补丁程序也会减少。

当提供单元测试但没有提供补丁时,我们通过将测试功能添加到套件来验证是否存在针对主干的错误。我们提交它,但是为它添加junit4 @Ignore注释,以免破坏已知错误的构建。

某些错误可能是相关的,或者对一个错误所做的更改可能会修复另一个错误。我们想知道以前已知的失败测试用例何时不再失败,以便我们可以通知观看该错误的人并关闭该错误。

假设我们有一些错误的功能 - 引发异常,具有无意的副作用或返回错误的值。 (Real world example

public void buggyFunction() {
    throw new UnsupportedOperationException("this will be implemented later");
}

进行一些单元测试以测试buggyFunction

@Test
public void knownIssue() {
    buggyFunction();
}

选项1 @Ignore测试

@Ignore("this test is currently failing. see bug 12345")
@Test
public void knownIssue() {
    buggyFunction();
}

选项2 :标准的junit4方式是在整个测试功能中使用@Test(expected=MyException.class)或使用@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();
}

OR

@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());
}

选项3 :用锅炉板脚手架进行测试

@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!");
}

有没有更好的选择:

  • 在已知问题打开时不破坏构建
  • 解决已知问题时通知我们
  • 优选地,只要已知问题是打开的,就算作跳过的测试
  • 最好是来自hamcrest或其他图书馆的开箱即用的解决方案

我可以在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())

此装饰器可以测试引发异常,返回错误值或产生错误副作用的已知问题。

这个装饰器是100%可重复使用的,所以没有copy-pasta try /除了脚手架,我可以在修复已知问题时删除一行代码,最重要的是我可以单独保留测试用例逻辑。

是否可以将其转换为Java 6或7?

3 个答案:

答案 0 :(得分:3)

创建一个单独的TestSuite错误识别器,只有当错误存在时才会通过(!)。

with det as 
(
select NVL(d1_masterid, d2_masterid) as masterid,  d1_id, d2_id
from   
        detail1
FULL OUTER JOIN
        detail2
on      d1_masterid = d2_masterid
and      d2_id =  d1_id
)
select distinct m.masterid, d1_id, d2_id
from 
  master m
LEFT OUTER JOIN 
 det
on m.masterid = det.masterid

当测试“失败”时,表示您可以将其删除,并取消缺陷。 当然,常规测试套件中需要进行反向测试。已删除/** * @see ...original now ignored test */ @Test public void defect123124() { expectThrows(UnsupportedOperationException.class, () -> buggyFunction()); }

现在你有一个很好的概述。程序员修复不同的东西,然后突然对错误套件的测试失败得到了他的奖金。

答案 1 :(得分:2)

除了您自己概述的选项之外,我没有看到其他技术选项。

我认为解决方案来自不同的视角:您的工具并不能为开发人员提供所需的反馈。你知道,重点是:当开发人员决定修复那个 buggyFunction()时,那需要以严格组织的方式进行。含义:

开发人员希望修复错误。所以他应该完全了解这个错误,以及与此任务相关的所有工作:

  • 在您的错误跟踪系统中进行更新
  • 更重要的是:运行所有测试!

换句话说:您希望您的开发人员收到快速反馈。他应该能够更改 buggyFunction ,然后,几分钟后,他应该收到有关现在测试用例失败的通知。

所以,即使他偶然修复了这个错误,关键的一点是他被通知他的改变在别处打破了测试。然后他的责任就是其他测试打破了什么。如果您的系统无法以有效的方式支持该工作流程,那么改变您的单元测试根本无济于事。因为你的单元测试是真正的问题。

从这个角度来看,唯一明智的选择是在选项2和3之间。因为它们会给你最好的"系统的这一部分可以给你什么:有人改变代码,然后测试中断。并尽快通知更改代码的人员;然后计算发生了什么。从那以后,它只是关于该测试的质量(指出当测试失败时它意味着什么);和相关人员的技能(然后做正确的事)。

答案 2 :(得分:1)

如果您正在使用Testsuites或使用它们,您可以选择JUnit Category注释。 (或者根据您的构建系统,您甚至可以在没有测试套件的情况下完成。查找链接文章的更多详细信息。)

因此,您需要创建一个标记界面:

public interface KnowIssue {
    // JUnit category marker interface
}

而不是添加@Ignore注释,而是将该类别应用于失败的测试方法(甚至是整个测试类):

@Test
@Category(KnowIssue.class)
public void myFailingTest() {
    // some test
}

然后在绿色测试的套件中排除KnownIssue类别:

@RunWith(Categories.class)
@ExcludeCategory(KnownIssue.class) // <- exclude known issues!
@Suite.SuiteClasses({ /** your Testclasses here */ })
public class AllMyGoodTests {
    // test suite
}

要查看您已知的问题,请创建第二个测试套件,然后您(通常)查看是否有任何已知问题因事件而得到修复(绿色):

@RunWith(Categories.class)
@IncludeCategory(KnownIssue.class) // <- only run known issues!
@Suite.SuiteClasses({ /** your Testclasses here */ })
public class KnownIssueTests {
    // test suite
}

然后您从该测试中删除该类别,并使用您的“好”自动执行该类别。再次测试。

甚至可以使用自定义test runner@RunWith(KnownIssueCategoryRunner.class)反转失败测试的测试结果(红色&lt; - &gt;绿色)但我从未尝试过这一点。