如何跳过失败的测试

Bru*_*ams -1 go go-testing

在 Go 中,你可以跳过已经失败的测试吗?

语境:

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

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

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

这是行不通的。如果测试失败,是否有办法追溯跳过测试?

Bru*_*ams 6

最简洁的答案是。您可以跳过测试或未通过测试,但不能两者兼而有之。

Go 的设计者认为尝试跳过测试是试图破坏测试框架,所以你不应该尝试这样做:

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

这是有记录的,但很容易被忽略:

如果测试失败(请参阅 Error、Errorf、Fail),然后被跳过,则仍被视为失败。

如果您有可靠的方法来检测 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 ")
    }
}
Run Code Online (Sandbox Code Playgroud)

您应该像这样构建您的测试:

// 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)
Run Code Online (Sandbox Code Playgroud)

这意味着您无法在单个测试中在多个执行和验证阶段之间交替,但最好的做法是在每个测试中仅具有单个执行和验证阶段。即四阶段测试

现在,如果你真的 这么做,你可以降低级别。这可能不是一个好主意,但为了完整性而包含在内。往兔子洞里看可能会表明你不想去那里。这考虑到了这个问题以及该testing包是如何实现的

    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
Run Code Online (Sandbox Code Playgroud)

如果这种级别的黑客行为还不足以让您相信这是一个多么糟糕的主意,您可能需要注意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  }
Run Code Online (Sandbox Code Playgroud)

您可以看到,一旦调用 Fail(),所有父测试也会被标记为失败。因此,如果您使用诸如testify/suite之类的东西将测试组织到套件中,则对于unfail测试,您还必须进行unfail父测试,但当且仅当套件中没有其他测试失败时。因此,更改testing()包以允许在失败后发生跳过与嵌套测试的想法相互作用很差。