Пройдите модульные тесты, бенчмарки, http-тесты

Go

Для нас, программистов, главным приоритетом должно быть улучшение качества кода. Вам нужно не только уметь писать бизнес-код из первых рук, но и обеспечивать его качество. Тестовые примеры — отличный инструмент для улучшения качества нашего кода.

С помощью теста мы можем вовремя обнаружить логические ошибки дизайна нашей программы и помочь другим программистам, которые возьмутся за проект, понять функцию.

В этой статье в основном представлен тестовый пакет на языке Go. это требует от нас*_test.goСоздайте новый файл сTestXxxНазовите функцию. затем пройтиgo test [flags] [packages]выполнить функцию.

$ ls
db.go
db_test.go

$ cat db_test.go
package db

import "testing"

func TestGetUser(t *testing.T) {
    user, err := GetUser("test@example.com")
    if err != nil {
        t.Fatal(err)
    }
    t.Log(user)
}

Он также предоставляет нам три типа функций: тестовая функция T, эталонная функция B и пример функции экземпляра.

Тестовый тест

Функциональный тест, основная сигнатура которого:

func TestName(t *testing.T){
    // ...
}

Имя тестовой функции должно начинаться сTestВ начале необязательное имя суффикса не должно начинаться со строчной буквы, как правило, с имени тестируемой функции.

Типыtesting.TСуществуют следующие методы:

// 打印日志。对于测试,会在失败或指定 -test.v 标志时打印。对与基准测试,总是打印,避免因未指定 -test.v 带来的测试不准确
func (c *T) Log(args ...interface{})
func (c *T) Logf(format string, args ...interface{})


// 标记函数失败,继续执行该函数
func (c *T) Fail()
// 标记函数失败,调用 runtime.Goexit 退出该函数。但继续执行其它函数或基准测试。
func (c *T) FailNow()
// 返回函数是否失败
func (c *T) Failed() bool


// 等同于 t.Log + t.Fail
func (c *T) Error(args ...interface{})
// 等同于 t.Logf + t.Fail
func (c *T) Errorf(format string, args ...interface{})


// 等同于 t.Log + t.FailNow
func (c *T) Fatal(args ...interface{})
// 等同于 t.Logf + t.FailNow
func (c *T) Fatalf(format string, args ...interface{})


// 将调用函数标记标记为测试助手函数。
func (c *T) Helper()

// 返回正在运行的测试或基准测试的名称
func (c *T) Name() string

// 用于表示当前测试只会与其他带有 Parallel 方法的测试并行进行测试。
func (t *T) Parallel()

// 执行名字为 name 的子测试 f,并报告 f 在执行过程中是否失败
// Run 会阻塞到 f 的所有并行测试执行完毕。
func (t *T) Run(name string, f func(t *T)) bool


// 相当于 t.Log + t. SkipNow
func (c *T) Skip(args ...interface{})
// 将测试标记为跳过,并调用 runtime.Goexit 退出该测试。继续执行其它测试或基准测试
func (c *T) SkipNow()
// 相当于 t.Logf + t.SkipNow
func (c *T) Skipf(format string, args ...interface{})
// 报告该测试是否是忽略
func (c *T) Skipped() bool

Контрольный тест

Функциональный тест, основная сигнатура которого:

func BenchmarkName(b *testing.B){
    // ...
}

Имя тестовой функции должно начинаться сBenchmarkВ начале необязательное имя суффикса не должно начинаться со строчной буквы, как правило, с имени тестируемой функции.

Тип B имеет параметр N, который можно использовать только для количества итераций для запуска теста. Бенчмарки и тесты, бенчмарки всегда выводят логи.

type B struct {
    N int
    // contains filtered or unexported fields
}

У бенчмарк-теста больше функций, чем у теста:

func (c *B) Log(args ...interface{})
func (c *B) Logf(format string, args ...interface{})
func (c *B) Fail()
func (c *B) FailNow()
func (c *B) Failed() bool
func (c *B) Error(args ...interface{})
func (c *B) Errorf(format string, args ...interface{})
func (c *B) Fatal(args ...interface{})
func (c *B) Fatalf(format string, args ...interface{})
func (c *B) Helper()
func (c *B) Name() string
func (b *B) Run(name string, f func(b *B)) bool
func (c *B) Skip(args ...interface{})
func (c *B) SkipNow()
func (c *B) Skipf(format string, args ...interface{})
func (c *B) Skipped() bool


// 打开当前基准测试的内存统计功能,与使用 -test.benchmem 设置类似,
// 但 ReportAllocs 只影响那些调用了该函数的基准测试。
func (b *B) ReportAllocs()

// 对已经逝去的基准测试时间以及内存分配计数器进行清零。对于正在运行中的计时器,这个方法不会产生任何效果。
func (b *B) ResetTimer()
例:
func BenchmarkBigLen(b *testing.B) {
    big := NewBig()
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        big.Len()
    }
}

// 以并行的方式执行给定的基准测试。RunParallel 会创建出多个 goroutine,并将 b.N 个迭代分配给这些 goroutine 执行,
// 其中 goroutine 数量的默认值为 GOMAXPROCS。用户如果想要增加非CPU受限(non-CPU-bound)基准测试的并行性,
// 那么可以在 RunParallel 之前调用 SetParallelism。RunParallel 通常会与 -cpu 标志一同使用。
// body 函数将在每个 goroutine 中执行,这个函数需要设置所有 goroutine 本地的状态,
// 并迭代直到 pb.Next 返回 false 值为止。因为 StartTimer、StopTimer 和 ResetTimer 这三个函数都带有全局作用,所以 body函数不应该调用这些函数;
// 除此之外,body 函数也不应该调用 Run 函数。
func (b *B) RunParallel(body func(*PB))
例:
func BenchmarkTemplateParallel(b *testing.B) {
    templ := template.Must(template.New("test").Parse("Hello, {{.}}!"))
    b.RunParallel(func(pb *testing.PB) {
        var buf bytes.Buffer
        for pb.Next() {
            buf.Reset()
            templ.Execute(&buf, "World")
        }
    })
}


// 记录在单个操作中处理的字节数量。 在调用了这个方法之后, 基准测试将会报告 ns/op 以及 MB/s
func (b *B) SetBytes(n int64)

// 将 RunParallel 使用的 goroutine 数量设置为 p*GOMAXPROCS,如果 p 小于 1,那么调用将不产生任何效果。
// CPU受限(CPU-bound)的基准测试通常不需要调用这个方法。
func (b *B) SetParallelism(p int)

// 开始对测试进行计时。
// 这个函数在基准测试开始时会自动被调用,它也可以在调用 StopTimer 之后恢复进行计时。
func (b *B) StartTimer()

// 停止对测试进行计时。
func (b *B) StopTimer()

Пример теста

Функция примера может помочь нам написать пример и сравнить его с выводом:

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

func ExampleSalutations() {
    fmt.Println("hello, and")
    fmt.Println("goodbye")
    // Output:
    // hello, and
    // goodbye
}

// 无序输出 Unordered output
func ExamplePerm() {
    for _, value := range Perm(4) {
        fmt.Println(value)
    }
    // Unordered output: 4
    // 2
    // 1
    // 3
    // 0
}

Что нам нужно знать о примере функции:

  • Подпись функции должна начинаться сExampleначало
  • Существует два вида сравнения вывода: упорядоченное (Output) и неупорядоченное (Unordered output)
  • Если функция не выводит комментарии, она не будет выполнена

Официальные правила именования для нас:

// 一个包的 example
func Example() { ... }
// 一个函数 F 的 example
func ExampleF() { ... }
// 一个类型 T 的 example
func ExampleT() { ... }
// 一个类型 T 的方法 M 的 example
func ExampleT_M() { ... }

// 如果以上四种类型需要提供多个示例,可以通过添加后缀的方式
// 后缀必须小写
func Example_suffix() { ... }
func ExampleF_suffix() { ... }
func ExampleT_suffix() { ... }
func ExampleT_M_suffix() { ... }

субтест

Выше мы также говорили о Тесте и Бенчмарке.Runметод, который используется для выполнения подтестов.

func TestFoo(t *testing.T) {
    // <setup code>
    t.Run("A=1", func(t *testing.T) { ... })
    t.Run("A=2", func(t *testing.T) { ... })
    t.Run("B=1", func(t *testing.T) { ... })
    // <tear-down code>
}

Каждый подтест может быть представлен уникальным именем: комбинацией имени теста верхнего уровня и последовательности имен, переданной в Run, с/разделены.

go test -run ''      # 运行所有测试
go test -run Foo     # 匹配 Foo 相关的顶级测试,如 TestFooBar
go test -run Foo/A=  # 匹配 Foo 相关的顶级测试, 并匹配子测试 A=
go test -run /A=1    # 匹配所有顶级测试, 并匹配它们的子测试 A=1

Подтесты также можно использовать для контроля параллелизма. Родительский тест может быть завершен только после завершения всех дочерних тестов. В этом примере все тесты параллельны друг другу и выполняются только друг с другом, независимо от других тестов верхнего уровня, которые могут быть определены:

func TestGroupedParallel(t *testing.T) {
    for _, tc := range tests {
        tc := tc // capture range variable
        t.Run(tc.Name, func(t *testing.T) {
            t.Parallel()
            ...
        })
    }
}

Выполнение не возвращается до завершения параллельных подтестов, что дает возможность очистки после набора параллельных тестов:

func TestTeardownParallel(t *testing.T) {
    // This Run will not return until the parallel tests finish.
    t.Run("group", func(t *testing.T) {
        t.Run("Test1", parallelTest1)
        t.Run("Test2", parallelTest2)
        t.Run("Test3", parallelTest3)
    })
    // <tear-down code>
}

Основной тест

Иногда нам также нужно начать тестирование с основной функции:

func TestMain(m *testing.M)

例:
func TestMain(m *testing.M) {
    // call flag.Parse() here if TestMain uses flags
    os.Exit(m.Run())
}

HTTP-тест

В настоящее время существует много веб-разработок на языке Go, поэтому после того, как мы протестировали функциональные функции, что нам делать с HTTP-тестированием?

Стандартная библиотека Go предоставляет намhttptestБиблиотека, с помощью которой можно легко выполнить HTTP-тестирование.

1. Проверьте функцию ручки

package main

import (
    "fmt"
    "io"
    "io/ioutil"
    "net/http"
    "net/http/httptest"
)

var HandleHelloWorld = func(w http.ResponseWriter, r *http.Request) {
    io.WriteString(w, "<html><body>Hello World!</body></html>")
}

func main() {
    req := httptest.NewRequest("GET", "http://example.com/foo", nil)
    w := httptest.NewRecorder()
    HandleHelloWorld(w, req)

    resp := w.Result()
    body, _ := ioutil.ReadAll(resp.Body)

    fmt.Println(resp.StatusCode)
    fmt.Println(resp.Header.Get("Content-Type"))
    fmt.Println(string(body))
}

2. TLS-сервер?

package main

import (
    "fmt"
    "io/ioutil"
    "log"
    "net/http"
    "net/http/httptest"
)

func main() {
    ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintln(w, "Hello, client")
    }))
    defer ts.Close()

    client := ts.Client()
    res, err := client.Get(ts.URL)
    if err != nil {
        log.Fatal(err)
    }

    greeting, err := ioutil.ReadAll(res.Body)
    res.Body.Close()
    if err != nil {
        log.Fatal(err)
    }

    fmt.Printf("%s", greeting)
}

3. Как тестировать распространенные HTTP-фреймворки?

// Package main provides ...
package main

import (
    "fmt"
    "net/http"
    "net/http/httptest"

    "github.com/gin-gonic/gin"
)

func main() {
    engine := gin.Default()
    engine.GET("/hello", func(c *gin.Context) { c.String(http.StatusOK, "Hello") })
    engine.GET("/world", func(c *gin.Context) { c.String(http.StatusOK, "world") })

    req := httptest.NewRequest(http.MethodGet, "/hello", nil)
    w := httptest.NewRecorder()

    engine.ServeHTTP(w, req)

    fmt.Println(w.Body.String())
}

Ссылка на эту статью:deepmagazine.com/post/study-…,Принять участие в обзоре »

--EOF--

Опубликовано 2018-05-09 23:31:00 и добавлено "go,test"Этикетка.

Другие статьи на тему "Серия шагов по карьерной лестнице"»