开放的编程资料库

当前位置:我爱分享网 > Go教程 > 正文

Golang中进行测试

Go测试教程展示了如何使用内置的测试包在Golang中进行测试。

$ go version
go version go1.18.1 linux/amd64

我们使用Go版本1.18。

单元测试是一个软件测试分支,测试软件的各个部分。单元测试的目的是验证软件的每个单元是否按设计执行。单元是任何软件中最小的可测试部分。单元测试不同于集成测试,后者将软件应用程序的不同单元和模块作为一个整体进行测试。

Go包含一个内置包testing用于进行测试。测试被写入以_test.go结尾的文件中。函数名的形式为

func TestXxx(*testing.T)

其中Xxx是要测试的函数的名称。

测试是通过gotest命令开始的。该命令编译程序和测试源并运行测试二进制文件。Gotest查找名称与文件模式*_test.go匹配的文件。最后会显示测试运行摘要。

Go测试文件可以包含测试函数、基准函数、模糊测试和示例函数。

Gotest可以在两种模式下运行:a)本地目录模式或b)包列表模式。当我们运行gotest时启用本地目录模式包参数。在这种模式下,gotest编译在当前目录中找到的包源和测试,然后运行生成的测试二进制文件。在此模式下缓存被禁用。

包列表模式在使用显式包名称运行命令gotest时启用;例如,gotest.gotest./...(目录树中的所有包)或gotestutils。在此模式下,gotest编译并测试命令行中列出的每个包。此外,它缓存成功的包测试结果,以避免不必要的重复运行测试

gotest-v,其中-v标志代表详细,打印出所有已执行的测试函数的名称及其执行时间。使用-coverage选项运行测试代码覆盖率。我们可以使用-run选项运行特定测试,我们在其中应用正则表达式定位函数名称。

进行简单测试

在第一个例子中,我们测试了两个简单的函数。

package main

func hello() string {

     return "Hello there!"
}

func morning() string {

     return "Good morning!"
}

这两个函数返回短文本消息。

package main

import "testing"

func TestHello(t *testing.T) {

     got := hello()
     want := "Hello there!"

     if got != want {

          t.Errorf("got %s, want %s", got, want)
     }
}

func TestMorning(t *testing.T) {

     got := morning()
     want := "Good morning!"

     if got != want {

          t.Errorf("got %s, want %s", got, want)
     }
}

文件的名称是message_test.go

import "testing"

testing包已导入。

func TestHello(t *testing.T) {

     got := hello()
     want := "Hello there!"

     if got != want {

          t.Errorf("got %s, want %s", got, want)
     }
}

被测试的函数前面有Test关键字。如果预期值和返回值不同,我们会写一条错误消息。

$ go test
PASS
ok   com.zetcode/first  0.001s

我们使用gotest命令运行测试。由于我们没有指定任何包名称,该工具会在当前工作目录中查找_test.go文件。通过发现的文件,它会查找具有TestXxx(*testing.T)签名的函数。

$ go test -v
=== RUN   TestHello
--- PASS: TestHello (0.00s)
=== RUN   TestMorning
--- PASS: TestMorning (0.00s)
PASS
ok  	com.zetcode/first	0.001s

要获取更多信息,我们使用-v选项。

$ go test -v -run Hello
=== RUN   TestHello
--- PASS: TestHello (0.00s)
PASS
ok  	com.zetcode/first	0.001s
$ go test -v -run Mor
=== RUN   TestMorning
--- PASS: TestMorning (0.00s)
PASS
ok  	com.zetcode/first	0.001s

我们通过将正则表达式模式传递给-run选项来运行特定功能。

去测试算术函数

在下一个例子中,我们将测试四个算术函数。

package main

func Add(x int, y int) int {

    return x + y
}

func Sub(x int, y int) int {

    return x - y
}

func Div(x float64, y float64) float64 {

    return x / y
}

func Mul(x int, y int) int {

    return x * y
}

我们有加法、减法、除法和乘法函数。

package main

import "testing"

func TestAdd(t *testing.T) {

     x, y := 2, 3
     want := 5

     got := Add(x, y)

     if got != want {

         t.Errorf("got %d, want %d", got, want)
     }
}

func TestSub(t *testing.T) {

     x, y := 5, 3
     want := 2

     got := Sub(x, y)

     if got != want {

         t.Errorf("got %d, want %d", got, want)
     }
}

func TestDiv(t *testing.T) {

     x, y := 7., 2.
     want := 3.5

     got := Div(x, y)

     if got != want {

         t.Errorf("got %f, want %f", got, want)
     }
}

func TestMul(t *testing.T) {

     x, y := 6, 5
     want := 30

     got := Mul(x, y)

     if got != want {

         t.Errorf("got %d, want %d", got, want)
     }
}

在每个函数中,我们提供测试值和预期输出。

$ go test -v
=== RUN   TestAdd
--- PASS: TestAdd (0.00s)
=== RUN   TestSub
--- PASS: TestSub (0.00s)
=== RUN   TestDiv
--- PASS: TestDiv (0.00s)
=== RUN   TestMul
--- PASS: TestMul (0.00s)
PASS
ok  	com.zetcode/math	0.001s

我们已经通过了所有四项测试。

$ go test -cover
PASS
coverage: 100.0% of statements
ok  	com.zetcode/math	0.001s

-cover选项提供有关测试覆盖了多少函数的信息。

$ go test -v -run "TestSub|TestMul"
=== RUN   TestSub
--- PASS: TestSub (0.00s)
=== RUN   TestMul
--- PASS: TestMul (0.00s)
PASS
ok  	com.zetcode/math	0.002s

使用管道运算符,我们选择两个特定的测试函数来运行。

Go表驱动测试

对于表驱动测试,我们有一个包含不同值和结果的表。测试工具迭代这些值并将它们传递给测试代码。这样我们就可以测试几种输入组合及其各自的输出。

这在其他语言中也称为参数化测试。

package main

type Val interface {
    int | float64
}

func Add[T Val](x T, y T) T {

    return x + y
}

func Sub[T Val](x T, y T) T {

    return x - y
}

func Div[T Val](x T, y T) T {

    return x / y
}

func Mul[T Val](x T, y T) T {

    return x * y
}

使用泛型;我们可以传递整数和浮点值作为参数。

package main

import "testing"

type TestCase[T Val] struct {
    arg1 T
    arg2 T
    want T
}

func TestAdd(t *testing.T) {

    cases := []TestCase[int]{
        {2, 3, 5},
        {5, 5, 10},
        {-7, 6, -1},
    }

    for _, tc := range cases {
        got := Add(tc.arg1, tc.arg2)
        if tc.want != got {
            t.Errorf("Expected '%d', but got '%d'", tc.want, got)
        }
    }
}

func TestSub(t *testing.T) {

    cases := []TestCase[int]{
        {2, 3, -1},
        {5, 5, 0},
        {-7, -3, -4},
    }

    for _, tc := range cases {
        got := Sub(tc.arg1, tc.arg2)
        if tc.want != got {
            t.Errorf("Expected '%d', but got '%d'", tc.want, got)
        }
    }
}

func TestDiv(t *testing.T) {

    cases := []TestCase[int]{
        {6., 3., 2.},
        {5., 5., 1.},
        {-10., 2., -5.},
    }

    for _, tc := range cases {
        got := Div(tc.arg1, tc.arg2)
        if tc.want != got {
            t.Errorf("Expected '%d', but got '%d'", tc.want, got)
        }
    }
}

func TestMul(t *testing.T) {

    cases := []TestCase[int]{
        {7, 3, 21},
        {5, 5, 25},
        {-1, 6, -6},
    }

    for _, tc := range cases {
        got := Mul(tc.arg1, tc.arg2)
        if tc.want != got {
            t.Errorf("Expected '%d', but got '%d'", tc.want, got)
        }
    }
}

我们的测试现在每个都有三个测试用例。

type TestCase[T Val] struct {
    arg1 T
    arg2 T
    want T
}

我们创建一个TestCase类型,其中包含输入值和预期输出的字段。

func TestAdd(t *testing.T) {

    cases := []TestCase[int]{
        {2, 3, 5},
        {5, 5, 10},
        {-7, 6, -1},
    }

    for _, tc := range cases {
        got := Add(tc.arg1, tc.arg2)
        if tc.want != got {
            t.Errorf("Expected '%d', but got '%d'", tc.want, got)
        }
    }
}

我们有一个包含三个测试用例的片段。我们遍历切片并为每种情况调用测试函数。

$ go test -v
=== RUN   TestAdd
--- PASS: TestAdd (0.00s)
=== RUN   TestSub
--- PASS: TestSub (0.00s)
=== RUN   TestDiv
--- PASS: TestDiv (0.00s)
=== RUN   TestMul
--- PASS: TestMul (0.00s)
PASS
ok  	com.zetcode/tables	0.001s

Go测试示例函数

可以添加用于运行一些基本测试和文档的示例函数。示例测试函数以示例词开头。

func ExampleHello() {
    fmt.Println("hello")
    // Output: hello
}

运行函数并将输出与输出字后面的值进行比较。

package main

func Add(x int, y int) int {

    return x + y
}

我们有一个简单的Add函数。

package main

import (
    "fmt"
    "testing"
)

func TestAdd(t *testing.T) {

    x, y := 2, 3
    want := 5

    got := Add(x, y)

    if got != want {

        t.Errorf("got %d, want %d", got, want)
    }
}

func ExampleAdd() {

    fmt.Println(Add(10, 6))
    // Output: 16
}

测试文件包含TestAdd函数和ExampleAdd示例测试函数。

$ go test -v
=== RUN   TestAdd
--- PASS: TestAdd (0.00s)
=== RUN   ExampleAdd
--- PASS: ExampleAdd (0.00s)
PASS
ok  	com.zetcode/example	0.002s

去httptest

httptest包包含用于测试HTTP流量的实用程序。

ResponseRecorderhttp.ResponseWriter的一个实现,它记录它的变化以供以后在测试中检查。

package main

import (
    "fmt"
    "log"
    "net/http"
)

func main() {

    http.HandleFunc("/", HelloHandler)

    log.Println("Listening...")
    log.Fatal(http.ListenAndServe(":8080", nil))
}

func HelloHandler(w http.ResponseWriter, _ *http.Request) {

    fmt.Fprintf(w, "Hello, there\n")
}

我们有一个带有一个HelloHandler的简单HTTP服务器。

package main

import (
    "fmt"
    "io"
    "net/http"
    "net/http/httptest"
    "strings"
    "testing"
)

func TestHelloHandler(t *testing.T) {

    want := "Hello there!"

    ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
        fmt.Fprintln(w, want)
    }))

    defer ts.Close()

    client := ts.Client()

    res, err := client.Get(ts.URL)

    if err != nil {
        t.Errorf("expected nil got %v", err)
    }

    data, err := io.ReadAll(res.Body)
    res.Body.Close()

    if err != nil {

        t.Errorf("expected nil got %v", err)
    }

    got := strings.TrimSpace(string(data))
    if string(got) != want {

        t.Errorf("got %s, want %s", got, want)
    }
}

TestHelloHandler中,我们使用httptest.NewServer启动测试服务器,并为HelloHandler实现一个相同的处理程序。客户端生成请求,并将响应与预期输出进行比较。在函数结束时,服务器关闭。

在本教程中,我们使用内置的测试模块在Go中执行了测试。

列出所有Go教程。

未经允许不得转载:我爱分享网 » Golang中进行测试

感觉很棒!可以赞赏支持我哟~

赞(0) 打赏