如何修复Go测试输出中的行号问题?

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

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

我有两个问题:

  1. 错误指向第12行-为什么?t.Error如何找出它被调用的行?

  2. 在辅助函数中,我想指定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:

  1. The error points to line 12 - why? How does t.Error find out which line it was called from?

  2. 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.Ttesting.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
}

huangapple
  • 本文由 发表于 2015年3月7日 07:30:01
  • 转载请务必保留本文链接:https://go.coder-hub.com/28909404.html
匿名

发表评论

匿名网友

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

确定