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