英文:
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 assertion将testing.T
转换为我们的myT
类型,因为类型断言要求表达式是接口类型!而t
不是接口类型,它是一个具体类型:*testing.T
。
解决方案非常简单,我们可以使用简单的type conversion,因为myT
和testing.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")
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论