【问题标题】:How to skip a failed test如何跳过失败的测试
【发布时间】:2020-06-19 08:05:51
【问题描述】:

在 go 中,是否允许跳过已经失败的测试?

上下文:

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

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

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

这不起作用。如果测试失败,有没有办法追溯跳过测试?

【问题讨论】:

  • 现在可能会更好地投入时间并使您的测试更加精细 - 从而能够跳过有问题的部分(以及依赖于这些部分的任何测试)。
  • 没有。 Go 对时间旅行的支持非常糟糕。
  • 您可以完全控制所报告的故障;如果您不希望在某些情况下将测试标记为失败,请不要致电Error/Fail

标签: go go-testing


【解决方案1】:

简短的回答是。您可以跳过测试或失败,但不能两者兼而有之。

go 的设计者认为试图跳过测试是试图颠覆测试框架 所以你不应该尝试这样做:

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

这是documented,但很容易错过:

如果测试失败(请参阅 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 ")
    }
}

您应该改用以下方式构建测试:

// 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 父测试,但 当且仅当套件失败了。因此,更改 testing() 包以允许在失败后发生跳过与嵌套测试的想法的交互性很差。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-01-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多