如何跳过失败的测试

时间:2020-03-06 14:56:05

标签: go go-testing

在进行中,您是否可以跳过已经失败的测试?

上下文:

我有一个heisenbug,目前无法确定原因。它会导致某些测试有时失败。 通过检查各种日志,我可以确定故障模式。我想做类似的事情:

if t.Failed() {
    if strings.Contains(string(suite.Stdout), "connection reset by peer") {
        t.Skip("Skip Test failed ")
    }
}

这些测试非常有价值,尽管我有heisenbug,我还是想在CI中运行它们,所以这只是一个临时解决方法。

这不起作用。有没有办法回顾测试是否失败?

1 个答案:

答案 0 :(得分:4)

简短的回答是。您可以跳过测试或通过测试,但不能同时失败。

go的设计者考虑尝试跳过测试,就像试图颠覆测试框架一样 因此您不应该尝试这样做:

例如参见https://github.com/golang/go/issues/16502

这是documented,但很容易错过:

如果测试失败(请参见错误,Errorf,失败),然后被跳过,则仍被视为失败。

如果您有可靠的检测heisenbug的方法,则应在进行任何测试断言之前运行它。因此,而不是:

// execute
executeThingBeingTested()

// verify
assert.Equal(t, expected, actual)

// recover if needed
if t.Failed() {
    // detect heisenbug
    if strings.Contains(string(suite.Stdout), "connection reset by peer") {
        t.Skip("Skip Test failed ")
    }
}

您应该改用以下结构:

// execute
executeThingBeingTested()

// skip if needed
if strings.Contains(string(suite.Stdout), "connection reset by peer") {
    t.Skip("Skip Test failed ")
}

// verify
assert.Equal(t, expected, actual)

这意味着您不能在一个测试中在多个执行和验证阶段之间进行切换,但是它的优良作法是无论如何在每个测试中都只有一个执行和验证阶段。即four phase testing

现在,如果您真的 真的想要这样做,则可以进入低级。这可能不是一个好主意,但为了完整起见,将其包括在内。往兔子洞里张望,可能有助于表明您不想去那里。这考虑了this question以及testing软件包如何implemented

    t := suite.T()

    // low-level hackery - undo the failed state so we can skip a test
    pointerVal := reflect.ValueOf(t)
    val := reflect.Indirect(pointerVal)
    member := val.FieldByName("failed")
    ptrToFailedFlag := unsafe.Pointer(member.UnsafeAddr())
    realPtrToFailedFlag := (*bool)(ptrToFailedFlag)
    *realPtrToFailedFlag = false

如果这种级别的黑客攻击不足以说服您这是一个坏主意,那么您在撰写本文时可能要注意fail()的实现:

        // Fail marks the function as having failed but continues execution.
   605  func (c *common) Fail() {
   606      if c.parent != nil {
   607          c.parent.Fail()
   608      }
   609      c.mu.Lock()
   610      defer c.mu.Unlock()
   611      // c.done needs to be locked to synchronize checks to c.done in parent tests.
   612      if c.done {
   613          panic("Fail in goroutine after " + c.name + " has completed")
   614      }
   615      c.failed = true
   616  }

您可以看到,一旦调用Fail(),任何父级测试也会被标记为失败。因此,如果您使用testify/suite之类的东西将测试组织到套件中,则要unfail测试,您还必须unfail父测试,但 if并且仅当套件中没有其他测试失败。因此,更改testing()程序包以允许在失败后跳过会与嵌套测试的概念发生不良交互。