英文:
How to fix line numbers in Go test output?
问题
让我们考虑这个简单的测试代码。
(注意:这里的assertSomething
非常简单,但通常我会编写一个更专门的辅助函数来处理当前任务,它会检查多个条件并报告多种类型的错误。)
package hello
import "testing"
func TestFoo(t *testing.T) {
assertSomething(t, 2+2 == 4) // 第6行
assertSomething(t, 2+3 == 6) // 第7行
}
func assertSomething(t *testing.T, expected bool) {
if !expected {
t.Error("Something's not right") // 第12行
}
}
当我运行go test
时,我得到以下结果:
--- FAIL: TestFoo (0.00s)
hello.go:12: Something's not right
FAIL
exit status 1
FAIL kos/hello 0.008s
我有两个问题:
-
错误指向第12行-为什么?
t.Error
如何找出它被调用的行? -
在辅助函数中,我想指定
t.Error
应该向上查找堆栈以确定要打印的行号,这样我将得到如下消息:
--- FAIL: TestFoo (0.00s)
hello.go:7: Something's not right
Python允许我在warnings.warn("message", stacklevel=2)
中做到这一点-我应该如何在这里实现相同的功能?
英文:
Let's consider this simple testing code.
(Note: assertSomething
is super simple here, but normally I'd write a more specialised helper for the task at hand that would look at multiple things and could report more than one type of error.)
package hello
import "testing"
func TestFoo(t *testing.T) {
assertSomething(t, 2+2 == 4) // line 6
assertSomething(t, 2+3 == 6) // line 7
}
func assertSomething(t *testing.T, expected bool) {
if !expected {
t.Error("Something's not right") // line 12
}
}
When I run go test
, I get the following:
--- FAIL: TestFoo (0.00s)
hello.go:12: Something's not right
FAIL
exit status 1
FAIL kos/hello 0.008s
I have two questions:
-
The error points to line 12 - why? How does
t.Error
find out which line it was called from? -
In the helper, I'd like to specify that
t.Error
should look stack level higher to determine the line number to print, so that I would get a message like this:--- FAIL: TestFoo (0.00s)
hello.go:7: Something's not right
Python allows me to do this, for instance, in warnings.warn("message", stacklevel=2)
- how would I implement the equivalent here?
答案1
得分: 17
自Go 1.9以来,发生了一些变化。
testing.T
和testing.B
中添加了Helper()
方法。它旨在从测试辅助函数(如assertSomething
)中调用,以指示该函数是一个辅助函数,我们对其产生的行号不感兴趣。
package main
import "testing"
func TestFoo(t *testing.T) {
assertSomething(t, 2+2 == 4) // 第6行
assertSomething(t, 2+3 == 6) // 第7行
}
func assertSomething(t *testing.T, expected bool) {
if !expected {
t.Helper()
t.Error("Something's not right") // 第12行
}
}
输出包含正确的行号:
=== RUN TestFoo
--- FAIL: TestFoo (0.00s)
main.go:7: Something's not right
FAIL
你也可以在Go Playground上尝试它。
英文:
Things have changed since go 1.9.
Helper()
method has been added to testing.T
and testing.B
. It's intended to be invoked from testing helpers such as assertSomething
to indicate the function is a helper and we're not interested in line numbers coming from it.
package main
import "testing"
func TestFoo(t *testing.T) {
assertSomething(t, 2+2 == 4) // line 6
assertSomething(t, 2+3 == 6) // line 7
}
func assertSomething(t *testing.T, expected bool) {
if !expected {
t.Helper()
t.Error("Something's not right") // line 12
}
}
The output contains correct line numbers:
=== RUN TestFoo
--- FAIL: TestFoo (0.00s)
main.go:7: Something's not right
FAIL
You can also try it on Go Playground.
答案2
得分: 12
你可以做你所要求的事情,并且可以通过查看源代码来了解t.Error
的工作原理。我认为你要找的是decorate
函数。
但是,如果你有大量的检查代码,并且由于某种原因在测试中重复出现,最好将其提取为一个返回错误的函数,而不是传入testing.T
并将其变为一个"assertion"。实际上,在语言的FAQ中明确不鼓励编写断言函数。
以下是我认为符合惯用测试代码的示例。它是表驱动的,测试案例包括输入和期望输出,当测试失败时,可以得到非常清晰的错误消息。
package hello
import "testing"
func TestFoo(t *testing.T) {
cases := []struct {
a, b, want int
}{
{2, 2, 4},
{2, 3, 6},
}
for _, c := range cases {
if got := operation(c.a, c.b); got != c.want {
t.Errorf("operation(%d, %d) = %d, want %d", c.a, c.b, got, c.want)
}
}
}
func operation(a, b int) int {
return a + b
}
你可以通过这里查看源代码。
英文:
You can do what you're asking, and you can find out how t.Error
works by looking at the source code. The function decorate
is what you're looking for I think.
But, in the case where you have significant amounts of checking code, and for some reason it's getting duplicated in your test, it's better to extract that as a function that returns an error than passing in a testing.T and making it an "assertion". Indeed, writing assertion functions is explicitly discouraged in the language FAQ.
package hello
import "testing"
func TestFoo(t *testing.T) {
if err := checkSomething(2+2 == 4); err != nil {
t.Errorf("2+2=4 failed: %s", err)
}
if err := checkSomething(2+3 == 6); err != nil {
t.Errorf("2+3=6 failed: %s", err)
}
}
func checkSomething(v bool) error {
if !v {
return errors.New("something's not right")
}
return nil
}
But here's what I think idiomatic testing code would look like. It's table-driven, and the cases include inputs and expected output, leading to really clear error messages when the tests fail.
package hello
import "testing"
func TestFoo(t *testing.T) {
cases := []struct {
a, b, want int
}{
{2, 2, 4},
{2, 3, 6},
}
for _, c := range cases {
if got := operation(c.a, c.b); got != c.want {
t.Errorf("operation(%d, %d) = %d, want %d", c.a, c.b, got, c.want)
}
}
}
func operation(a, b int) int {
return a + b
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论