เขียน test บน go ด้วย testify

จริง ๆ ใน golang มี package ชื่อ testing อยู่ ใช้ตัวนั้นก็ได้ ตรงไปตรงมาดี ความซับซ้อนก็จะเริ่มมาตอนที่เราอยากจะทำอะไรที่ยากขึ้น เช่น การทำ test double หรือการเปรียบเทียบ struct 2 ตัว ซึ่งจริง ๆ แล้วเราสามารถเอาพวก reflect มาใช้เปรียบเทียบ struct ได้ ส่วนการทำ test double ก็จะซับซ้อนกว่านั้น

testify เป็น package set ที่เตรียมของพวกนี้ไว้ให้เราใช้ test package ของเราง่ายขึ้น

ใน testify มี package ย่อย ๆ ให้เราใช้หลายตัว

assert

ปกติถ้าเราใช้แค่ testing package ตอนเราอยากจะ assert อะไร เราก็ใช้การเปรียบเทียบธรรมดา เช่น

if res != "OK" {
    t.Errorf("expecting result to be OK but %v", res)
}

แต่ใน testify เราสามารถเขียนได้ด้วย

assert.Equal(t, "OK", res, "expecting result to be OK")

ที่สำคัญ ถ้าค่าที่เราต้องการเปรียบเทียบเป็น struct โดยปกติเราจะใช้ reflect.DeepEqual ในการเปรียบเทียบ เช่น

if !reflect.DeepEqual(exp, res) {
    t.Errorf("expecting result to be %v but %v", exp, res)
}

แต่ตัว assert.Equal เราสามารถส่ง struct object เข้าไปได้เลย แถมตอนที่เปรียบเทียบ struct object มันยังบอกจุดที่แตกต่างกันให้อีก ไม่ต้องมานั่งลูปหาเองว่าต่างกันตรงไหน เช่น

package my_test

import (
    "testing"

    "github.com/stretchr/testify/assert"
)

type Address struct {
    Street   string
    PostCode string
}

type Person struct {
    Name        string
    Age         int
    Nationality string
    Address
}

func TestSomething(t *testing.T) {
    assert.Equal(t, Person{
        Name:        "a",
        Age:         17,
        Nationality: "Thai",
        Address: Address{
            Street:   "123 Abc",
            PostCode: "12370",
        },
    }, Person{
        Name:        "b",
        Age:         24,
        Nationality: "Thai",
        Address: Address{
            Street:   "345 Back",
            PostCode: "12370",
        },
    }, "they should be equal")
}

ในตัว assert package เองมี method ที่ช่วยให้เรา assert ของได้ง่ายขึ้นอีกหลายตัว สามารถเข้าไปดู method ทั้งหมดได้ที่ https://godoc.org/github.com/stretchr/testify/assert

require

มี method เหมือน assert แต่แค่ถ้า fail มันจะหยุด test ทันที

mock

ทำเหมือนการทำ dependency injection ทั่วไป คือ เราจะสร้าง mock จาก interface ดังนั้นสร้าง interface ไว้ก่อน และสร้าง struct ที่มี method ที่ implement interface นั้น เป็น code ที่ทำงานจริง ๆ แล้วก็ decouple variable ของที่อยู่ใน struct หรือ package แล้ว inject interface นั้นเข้ามาในฐานะ dependency แทน

ตอนเขียน mock ใน test ก็ทำประมาณนี้

type mockedObject struct {
    mock.Mock
}

// implement method ใน interface
func (m *mockedObject) DoSomething(number int) (bool, error) {
    // track behavior เพื่อ spy
    args := m.Called(number)

        // return ของตามที่ประกาศไว้ใน stub
    return args.Bool(0), args.Error(1)
}

ตอนเขียน test ก็ประมาณนี้

func TestSomething(t *testing.T) {
    // สร้าง mock
    o := new(mockedObject)

    // spy
    o.On("DoSomething", 123).Return(true, nil)

    // action
    MethodUnderTest(o)

    // assert ว่ามีการเรียกของที่ spy ไว้มั๊ย
        o.AssertExpectations(t)
}

ใน mock package มีของให้เล่นอีกเพียบ ลองเข้าไปดูกันได้ที่ https://godoc.org/github.com/stretchr/testify/mock

suite

ใช้จับกลุ่ม test cases ในไฟล์เป็น test suite เช่น

// ประกาศ test suite และตัวแปรที่ใช้ใน test suite
type TestSuite struct {
    suite.Suite
    VariableThatShouldStartAtFive int
}

// test setup ประกาศให้ตัวแปรมีค่าเป็น 5
func (suite *TestSuite) SetupTest() {
    suite.VariableThatShouldStartAtFive = 5
}

// test case จะขึ้นต้นด้วยคำว่า Test
func (suite *TestSuite) TestExample() {
    assert.Equal(suite.T(), 5, suite.VariableThatShouldStartAtFive)
}

// สร้าง test wrapper มาครอบ test suite ไว้ เพื่อให้ตอนสั่ง go test มันมาเรียก test suite ของเราได้
func TestExampleTestSuite(t *testing.T) {
    suite.Run(t, new(TestSuite))
}

เข้าไปดู method อื่น ๆ ของ suite เช่นพวก suite setup, suite teardown ได้ที่ https://godoc.org/github.com/stretchr/testify/suite

ทีนี้เขียน test ง่ายขึ้นอีกเยอะเลย ใครอยากลองใช้เข้าไปดูได้ที่ https://github.com/stretchr/testify เลยครับ

Leave a Reply

Your email address will not be published. Required fields are marked *