😺

Goのtestingを使ったテストで失敗したテストを無理矢理成功させる

2023/02/17に公開

発端

godogで失敗するのを前提としたTestSuiteを成功状態にしたかった。

解決案

testing.T.Failなどが呼び出された状態になっており、この呼び出しまえの状態に戻してしまえばいい。
testing.T型に失敗した情報が保持されているので、これを書き換える。
具体的には T.common.failedT.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)

https://cs.opensource.google/go/go/+/refs/tags/go1.20.1:src/testing/testing.go;l=898-902;bpv=0;bpt=0

type T struct {
	common
	isEnvSet bool
	context  *testContext // For running tests and subtests.
}

commonをもっている。

https://cs.opensource.google/go/go/+/refs/tags/go1.20.1:src/testing/testing.go;l=592-630;drc=1a9893a969e0a73dc4f1e48ed40ccaf29ec238a6;bpv=0;bpt=0

type common struct {
// 一部省略
	failed      bool                 // Test or benchmark has failed.
	parent   *common
}

commonにはfailedがあり、parentがcommonなのでcommonにもfailedがある

https://cs.opensource.google/go/go/+/refs/tags/go1.20.1:src/testing/testing.go;l=925-936

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になる。

https://cs.opensource.google/go/go/+/refs/tags/go1.20.1:src/testing/testing.go;l=1590-1639;drc=1a9893a969e0a73dc4f1e48ed40ccaf29ec238a6;bpv=0;bpt=1

func (t *T) Run(name string, f func(t *T)) bool {
	// (中略)
	// Runが成功したかどうかは t.failedを見ている。
	return !t.failed
}

Runしたときなどはこの値をみている。Run中にサブテストを呼んでんでいれば、親のテストは失敗したことになりそう。

もっとネストしているとparentをたどっていく再帰的な処理をいれたほうがよいだろう。

reflectの説明は、長くなりそうなのでここではやめておく。

参考文献

https://stop-the-world.hatenablog.com/entry/2019/12/31/214058
https://pkg.go.dev/reflect

Discussion