扩展现有的 type testing.T 以添加额外的检查。

huangapple go评论85阅读模式
英文:

Extending existing type testing.T for adding extra checks

问题

我意识到我可以将testing.T类型传递给需要处理测试接口的函数。但是,如果扩展结构体并使用它呢?我知道可以使用类型扩展,但不幸的是这样不起作用:

package test

import "testing"

type testingT testing.T

func (t *testingT) assert(val int) {
    if val == 0 {
        t.Errorf("%d is zero", val)
    }
}

func TestSomething(t *testingT) {
    t.assert(0)
}
英文:

I realize I could pass the testing.T type to the functions that need to deal with the testing interfaces. But how about extending the struct and using it? I know I can type extend but this doesn't work unfortunately:

package test

import "testing"

type testingT testing.T

func (t *testingT) assert(val int) {
	if val == 0 {
		t.Errorf("%d is zero", val)
	}
}

func TestSomething(t *testingT) {
	t.assert(0)
}

答案1

得分: 3

一种解决方案是使用嵌入,就像你在eduncan911的答案中看到的那样。如果你想要向新的结构体添加其他字段,这是一个很好的方法。

然而,这不是必需的。创建一个以testing.T作为其底层类型的新类型(就像你所做的那样)就足够了:

type myT testing.T

func (t *myT) assert(val int) {
	if val == 0 {
		t.Errorf("%d is zero", val)
	}
}

使用它

TestSomething()中,我们不能直接调用t.assert(0),因为assert()不是testing.T的方法,而是我们的myT类型的方法。所以我们需要一个myT类型的值,我们可以在其上调用myT.assert()

然而,我们不能使用type assertiontesting.T转换为我们的myT类型,因为类型断言要求表达式是接口类型!而t不是接口类型,它是一个具体类型:*testing.T

解决方案非常简单,我们可以使用简单的type conversion,因为myTtesting.T具有相同的底层类型:

my := myT(*t)   // 转换有效,但还不够,继续阅读...

上述方法可以工作,我们可以调用:

my.assert(0)

但是在运行测试时,它不会失败!为什么呢?因为上述的*t解引用、转换和赋值会创建一个副本my将是myT类型的副本,所以当我们调用myT.assert()时,它得到的是一个指针,但是指向副本。当myT.assert()调用t.Errorf()时,它将在副本上注册错误,因此测试框架将不会看到这个Errorf()调用。

"最终"的解决方案很简单:使用类型转换,但不是将*t转换,而是转换t本身(它是一个指针)。当然,*t不能转换为myT,只能转换为*myT。而且这个类型需要用括号括起来(以避免歧义):

func TestSomething(t *testing.T) {
	my := (*myT)(t)
	my.assert(0)
}

现在我们很高兴,一切都按预期工作。

**注意:**如果你不想调用myT的多个方法,你也可以在一行中调用myT.assert()

func TestSomething(t *testing.T) {
	(*myT)(t).assert(0)
}
英文:

One solution is embedding as you can see in eduncan911's answer. It's great if you want to add other fields to the new struct.

However, this is not a requirement. Creating a new type having testing.T as its underlying type (just as you did) is just enough:

type myT testing.T

func (t *myT) assert(val int) {
	if val == 0 {
		t.Errorf("%d is zero", val)
	}
}

Using it

In TestSomething() we can't just call t.assert(0) because assert() is not a method of testing.T but our myT type. So we need a value of type myT on which we can call myT.assert().

We can't use type assertion to convert a testing.T to our myT type though, because type assertion requires the expression to be of interface type! And t is not an interface type, it's a concrete type: *testing.T.

The solution is really simple, we can use simple type conversion because myT and testing.T has the same underlying type:

my := myT(*t)   // Conversion valid, but not yet enough, read on...

The above works, and we can call:

my.assert(0)

But when running tests, it won't fail! Why is that? It's because the above *t dereferencing, conversion and assignment will make a copy, and my will be of type myT but will be a copy of *t, and so when we call myT.assert(), it gets a pointer, but a pointer to the copy. And when myT.assert() calls t.Errorf(), it will register the error on a copy, so the testing framework will not see this Errorf() call.

The "final" solution is simple: use type conversion, but don't convert *t, but t itself (which is a pointer). Of course *t cannot be converted to myT, only to *myT. And this type needs to be parenthesized (to avoid ambiguity):

func TestSomething(t *testing.T) {
	my := (*myT)(t)
	my.assert(0)
}

And now we're happy, everything works as expected.

Note: You can also call myT.assert() in one line if you don't want to call multiple methods of myT:

func TestSomething(t *testing.T) {
	(*myT)(t).assert(0)
}

答案2

得分: 1

你可以使用embedding来添加自己的方法:

package test

import "testing"

type testingT struct {
	*testing.T
}

func (t *testingT) assert(val int) {
	if val == 0 {
		t.Errorf("%d is zero", val)
	}
}

func TestSomething(t *testing.T) {
	t2 := &testingT{t}
	t2.assert(0)
}

这段代码使用了embedding的方式,在testingT结构体中嵌入了*testing.T,从而可以在testingT中使用*testing.T的方法和属性。在testingT中定义了一个assert方法,用于断言val的值是否为零。在TestSomething函数中,创建了一个testingT对象t2,并调用了其assert方法来进行断言。

英文:

You can use embedding which would allow you to add your owner methods:

package test

import "testing"

type testingT struct {
	*testing.T
}

func (t *testingT) assert(val int) {
	if val == 0 {
		t.Errorf("%d is zero", val)
	}
}

func TestSomething(t *testing.T) {
	t2 := &testingT{t}
	t2.assert(0)
}

答案3

得分: 0

准备自定义测试结构体:

type T testing.T

func (t *T) AssertEqualsInt(expected, actual int) {
	if actual != expected {
		// 出错时的文件和行号
		t.Helper()
		t.Errorf("值不相等,期望:%d,实际:%d", expected, actual)
	}
}

func (t *T) AssertEqualsString(expected, actual string) {
	if actual != expected {
		t.Helper()
		t.Errorf("值不相等,期望:%s,实际:%s", expected, actual)
	}
}

然后在测试中使用它:

func TestFooBarBaz(t *testing.T) {
	(*T)(t).AssertEqualsInt(0, 10)
	(*T)(t).AssertEqualsString("foo", "bar")
}
英文:

Prepare custom testing struct:

type T testing.T

func (t *T) AssertEqualsInt(expected, actual int) {
	if actual != expected {
        // Correct point for file and line where happened error
		t.Helper()
		t.Errorf("Values not equal expected: %d, got: %d", expected, actual)
	}
}

func (t *T) AssertEqualsString(expected, actual string) {
	if actual != expected {
		t.Helper()
		t.Errorf("Values not equal expected: %s, got: %s", expected, actual)
	}
}

Than use it in testing:

func TestFooBarBaz(t *testing.T) {
    (*T)(t).AssertEqualsInt(0, 10)
    (*T)(t).AssertEqualsString("foo", "bar")
}

huangapple
  • 本文由 发表于 2016年4月15日 11:41:26
  • 转载请务必保留本文链接:https://go.coder-hub.com/36637858.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定