😺
Goのtestingを使ったテストで失敗したテストを無理矢理成功させる
発端
godogで失敗するのを前提としたTestSuiteを成功状態にしたかった。
解決案
testing.T.Fail
などが呼び出された状態になっており、この呼び出しまえの状態に戻してしまえばいい。
testing.T型に失敗した情報が保持されているので、これを書き換える。
具体的には T.common.failed
とT.parent.failed
をfalseにしてしまえば良さそうである。
しかし、非公開フィールドなのでreflectを用いてなんとかした。
以下のようなコードを書いてごまかしてみた。
func ForceSuccess(t *testing.T) {
v := reflect.ValueOf(t).Elem()
common := v.FieldByName("common")
setSuccess(common)
parent := v.FieldByName("parent").Elem()
setSuccess(parent)
}
func setSuccess(v reflect.Value) {
failed := v.FieldByName("failed")
p := (*bool)(unsafe.Pointer(failed.UnsafeAddr()))
*p = false
}
もうちょっと詳しく
testing.Tの定義を確認する(go 2.20.1)
type T struct {
common
isEnvSet bool
context *testContext // For running tests and subtests.
}
commonをもっている。
type common struct {
// 一部省略
failed bool // Test or benchmark has failed.
parent *common
}
commonにはfailedがあり、parentがcommonなのでcommonにもfailedがある
func (c *common) Fail() {
if c.parent != nil {
c.parent.Fail()
}
c.mu.Lock()
defer c.mu.Unlock()
// c.done needs to be locked to synchronize checks to c.done in parent tests.
if c.done {
panic("Fail in goroutine after " + c.name + " has completed")
}
c.failed = true
}
Failすると親もfailedがtrueになる。
func (t *T) Run(name string, f func(t *T)) bool {
// (中略)
// Runが成功したかどうかは t.failedを見ている。
return !t.failed
}
Runしたときなどはこの値をみている。Run中にサブテストを呼んでんでいれば、親のテストは失敗したことになりそう。
もっとネストしているとparentをたどっていく再帰的な処理をいれたほうがよいだろう。
reflectの説明は、長くなりそうなのでここではやめておく。
参考文献
Discussion