5 kỹ thuật nâng cao testing trong golang

December 10, 2018
testing Golang

Mặc định Go đã tích hợp sẵn thư viện testing. Nếu bạn đã từng viết Golang chắc hẳn bạn đã biết điều này. Tuy nhiên dưới đây sẽ là 5 kỹ năng nâng cao để bạn vận dụng nó cho việc testing ứng dụng lớn của bạn

Sử dụng Test suites


type Something interface {
    Do(input string) (Result, error)
}

// Suite tests sẽ test qua tất cả function mà inteface Something được implement
func Suite(t *testing.T, impl Something) {
    res, _ := impl.Do("thing")
    if res != expected {
        t.Fail("unexpected result")
    }
}

// Test function đầu tiên mà inteface Something implement
func Testfirst(t *testing.T) {
    one := one.NewOne()
    Suite(t, one)
}

// Test function khác mà inteface Something implement
func TestOther(t *testing.T) {
    other := other.NewOther()
    Suite(t, other)
}

Trong ví dụ trên các bạn có thể thấy chúng ta sử dụng suite để có thể test các function khác nhau được implement Type Something

Avoid interface pollution

Trong việc testing thì interfaces được đánh giá là khá quan trọng. Interfaces thì rất tuyệt vời nhưng interface pollution thì không.


type acknowledger interface {
    Ack(sub string, id ...string) error
}

type mockClient struct{}

func (c *mockClient) Ack(sub string, id ...string) error {
    return nil
}

var acker acknowledger = pubsub.New(...)

acker = &mockClient{} // in the test package

Don’t export concurrency primitives

GO cung cấp các concurrency primitives dễ sử dụng.

type Reader struct {...}
func (r *Reader) ReadChan() <-chan Msg {...}

Ví dụ ta có 1 phương thức sử dụng channel, xử lý đồng thời

Để testing phương thức trên ta có thể sử dụng như sau

func TestConsumer(t testing.T, q queueIface) {
    cons := &Consumer{
        r: q,
    }
    for {
        select {
        case msg := <-cons.r.ReadChan():
            // Test thing.
        case err := <-cons.r.ErrChan():
            // What caused this again?
        }
    }
}

Use net/http/httptest

httptest cho phép bạn làm việc với http.Handle mà ko cần mở port tới máy chủ thực sự. Điều này tăng tốc độ test và cho phép chay song song


func TestServe(t *testing.T) {
    // The method to use if you want to practice typing
    s := &http.Server{
        Handler: http.HandlerFunc(ServeHTTP),
    }
    // Pick port automatically for parallel tests and to avoid conflicts
    l, err := net.Listen("tcp", ":0")
    if err != nil {
        t.Fatal(err)
    }
    defer l.Close()
    go s.Serve(l)

    res, err := http.Get("http://" + l.Addr().String() + "/?sloths=arecool")
    if err != nil {
        log.Fatal(err)
    }
    greeting, err := ioutil.ReadAll(res.Body)
    res.Body.Close()
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(string(greeting))
}

// Use httptest
func TestServeMemory(t *testing.T) {
    // Less verbose and more flexible way
    req := httptest.NewRequest("GET", "http://example.com/?sloths=arecool", nil)
    w := httptest.NewRecorder()

    ServeHTTP(w, req)
    greeting, err := ioutil.ReadAll(w.Body)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(string(greeting))
}

Với 2 ví dụ ở trên ta thấy được sử dụng httptest code sẽ ngắn gọn hơn khá nhiều, ngoài ra nó còn không cần thiết phải mở port và vẫn có thể thực hiện test các phương thức liên quan đến http.Handler

Sử dụng separate _test package

Hầu hết hệ sinh thái trong môi trường test được tạo ra từ pkg_test.go, nhưng vẫn nằm trong package pkg. package test separate là package bạn tạo ra từ 1 file mới foo_test.go, nằm tại bên trong folder test/foo/. Từ đây bạn có thể import từ github github.com/example/foo và các dependencies khác. Nó giải quyết được vấn đề phụ thuộc theo chu kỳ trong test và cho phép các developer muốn sử dụng package của riêng họ

Cuối cùng điều này hỗ trợ trong việc tránh các chu kỳ trong test. Hầu hết các package phụ thuộc vào các package khác mà bạn viết ngoài những gói đang được testing. Ví dụ package net/url đã được implements URL parse mà package net/http sử dụng. Tuy nhiên net/url muốn sử dụng test bằng cách import net/http vậy nên package net/url_test được sinh ra

Bây giờ khi bạn sử dụng một separate test package, bạn có thể yêu cầu quyền access vào các entities trong package của bạn nơi chúng có thể truy cập trươc đó. Chúng ta có thể sử dụng một file bổ sung để thực thi vì các file _test.go sẽ bị loại trừ khi build