Go单元测试

单元测试是用来测试包或者程序的一部分代码或者一组代码的函数。测试的目的是确认目标代码在给定的场景下,有没有按照期望工作。

基础单元测试

以下是一个测试http包的Get函数的单元测试例子:

// main_test.go
package main

import (
    "net/http"
    "testing"
)

const checkMark = "\u2713"
const ballotX = "\u2717"

func TestDownload(t *testing.T) {
    url := "http://www.puasu.com/"
    statusCode := 200

    t.Log("Given the need to test downloading content.")
    {
        t.Logf("\tWhen checking \"%s\" for status code \"%d\"", url, statusCode)
        {
            resp, err := http.Get(url)
            if err != nil {
                t.Fatal("\t\tShould be able to make the Get call.", ballotX, err)
            }
            t.Log("\t\tShould be able to make the Get call.", checkMark)
            defer resp.Body.Close()

            if resp.StatusCode == statusCode {
                t.Logf("\t\tShould receive a \"%d\" status. %v", statusCode, checkMark)
            } else {
                t.Errorf("\t\tShould receive a \"%d\" status. %v %v", statusCode, ballotX, resp.StatusCode)
            }
        }
    }
}

通过命令go test -v来运行这个测试(-v表示提供冗余输出),得到如下的测试结果:

=== RUN   TestDownload
--- PASS: TestDownload (0.45s)
    main_test.go:15: Given the need to test downloading content.
    main_test.go:17:    When checking "http://www.puasu.com/" for status code "200"
    main_test.go:23:            Should be able to make the Get call. ✓
    main_test.go:27:            Should receive a "200" status. ✓
PASS
ok      learngo 0.461s

Go语言的testing包提供了从测试框架到报告测试的输出和状态的各种测试功能的支持,要使用这个框架,需要遵循以下几点规则:

  • 含有单元测试代码的go文件必须以_test.go结尾,Go语言测试工具只认符合这个规则的文件。
  • 单元测试文件名_test.go前面部分最好和被测试方法所在的go文件的文件名相对应,例如main.gomain_test.go
  • 一个测试函数必须是公开的函数,并且以Test单词开头(最好是Test+要测试的方法/函数名),而且函数的签名必须接收一个指向testing.T类型的指针,并且不返回任何值。
  • 测试的输出应该当成是代码文档的一部分,测试输出使用完整易读的语句,来记录为什么需要这个测试,具体测试了什么,以及测试的结果是什么。
  • 如果执行go test的时候没有加入冗余选项(-v),除非测试失败,否则我们是看不到任何测试输出的(即看不到t.Log系列方法的输出)。
  • t.Fatal方法会报告这个单元测试已经失败,而且会向测试输出写一些消息,而后立刻停止这个测试函数的执行。如果除了这个函数外还有其它没有执行的测试函数,会继续执行其它测试函数。如果需要报告测试失败,但是并不想停止当前测试函数的执行,可以使用t.Error系列方法。
  • 如果测试函数执行时没有调用过t.Fatal或者t.Error系列方法,就会认为测试通过了。

表组测试

如果测试可以接受一组不同的输入并产生不同的输出的代码,那么应该使用表组测试的方法进行测试。表组测试除了会有一组不同的输入值和期望结果之外,其余部分都很像基础单元测试。

将上述的例子改为表格驱动测试:

// main_test.go
package main

import (
    "net/http"
    "testing"
)

const checkMark = "\u2713"
const ballotX = "\u2717"

func TestDownload(t *testing.T) {
    urls := []struct {
        url        string
        statusCode int
    }{
        {
            "http://www.puasu.com/",
            http.StatusOK,
        },
        {
            "http://www.puasu.com/testing",
            http.StatusNotFound,
        },
    }

    t.Log("Given the need to test downloading  different content.")
    {
        for _, u := range urls {
            t.Logf("\tWhen checking \"%s\" for status code \"%d\"", u.url, u.statusCode)
            {
                resp, err := http.Get(u.url)
                if err != nil {
                    t.Fatal("\t\tShould be able to make the Get call.", ballotX, err)
                }
                t.Log("\t\tShould be able to make the Get call.", checkMark)

                if resp.StatusCode == u.statusCode {
                    t.Logf("\t\tShould receive a \"%d\" status. %v", u.statusCode, checkMark)
                } else {
                    t.Errorf("\t\tShould receive a \"%d\" status. %v %v", u.statusCode, ballotX, resp.StatusCode)
                }
                resp.Body.Close()
            }
        }
    }
}

执行go test -v,输出测试结果:

=== RUN   TestDownload
--- PASS: TestDownload (0.36s)
    main_test.go:26: Given the need to test downloading  different content.
    main_test.go:29:    When checking "http://www.puasu.com/" for status code "200"
    main_test.go:35:            Should be able to make the Get call. ✓
    main_test.go:38:            Should receive a "200" status. ✓
    main_test.go:29:    When checking "http://www.puasu.com/testing" for status code "404"
    main_test.go:35:            Should be able to make the Get call. ✓
    main_test.go:38:            Should receive a "404" status. ✓
PASS
ok      learngo 0.375s

reference:

《Go语言实战》