Intro Golang implement Unit test ฉบับสมบูรณ์

l3aseron By M
Touch Technologies
Published in
3 min readOct 12, 2021
touchtechnologies

System requirements

How to unit test ?

unit test คือ implememt code เพื่อใช้ test mechanism ของส่วนต่างๆของ Service ไม่ว่าจะเป็นส่วนของ handler service repository หรือ ว่าส่วนที่เชื่อมต่อ external interfaces ต่างก็สามารถเขียน unit test ได้

ข้อดีของการเขียน Unit test

  • ทำให้ลดการผิดพลาดของ mechanism ของ service เราได้
  • ทำให้คนที่มาเขียนต่อเข้าใจ mechanism โดยสามารถอ่านจาก unit test ได้
  • เพิ่มความมั่นใจมากขึ้นเมื่อต้องการ implement หรือ fix ในส่วนนั้นๆว่าจะไม่กระทบจุดอื่นๆ

แนวคิดของการเริ่มเขียนTest และ การออกแบบ

เราควรเริ่มเขียน Test ตั้งแต่ตรงไหนบาง

  • Case success คือเขียนค่าที่ถูกต้องเพื่อป้อนไปให้ Function ที่เรา implement ไว้ให้Output ออกมาแบบที่เราคิดไว้ จุดที่เราควรเขียนคือ
    1. จุดที่เราเขียน condition ต่างๆเอาไว้
    2. จุดที่เราเขียน การ maping data ต่างๆ
    3. จุดที่เราทำการ Validate
  • Case Fail คือเขียนค่าที่ไม่ถูกต้องเพื่อป้อนไปให้ Function ที่เรา implement ไว้ให้Output ออกมาแบบที่เราเขียน ดัก Case error ไว้ จุดที่เราควรเขียนคือ
    1. จุดที่เราเขียน condition ต่างๆเอาไว้
    2. จุดที่เราเขียน การ maping data ต่างๆ
    3. จุดที่เราทำการ Validate
    4. จุดที่ มีโอกาศ ได้ค่าว่าง เช่น การGet ข้อมูลจากที่ต่างๆ ไม่ว่าจะเป็น Database หรือ 3rd Party
    5. จุดที่มีโอกาส เกิด panic

Mockery ทำอะไรได้บาง

mockery ใช้สำหรับ autogenerator mock โดย mockery จะทำการ generate จาก interface ที่เราเขียนไว้ โดยมีตัวอย่างดังนี้

package company

import (
"context"
"github.com/touchtechnologies-product/go-blueprint-clean-architecture/domain"
"github.com/touchtechnologies-product/go-blueprint-clean-architecture/service/company/out"

"github.com/touchtechnologies-product/go-blueprint-clean-architecture/service/company/companyin"
)
//โดยตัว Mockery จะดูจากส่วนนี้
//go:generate mockery --name=Service
type Service interface {
List(ctx context.Context, opt *domain.PageOption) (total int, items []*out.CompanyView, err error)
Create(ctx context.Context, input *companyin.CreateInput) (ID string, err error)
Read(ctx context.Context, input *companyin.ReadInput) (company *out.CompanyView, err error)
Update(ctx context.Context, input *companyin.UpdateInput) (err error)
Delete(ctx context.Context, input *companyin.DeleteInput) (err error)
}

Command ที่ใช้ Generate

go generate ./...
หลังจาก generate เราจะได้ Folder mocks ขึ้นมา

รู้จักกับAssertต่างๆ ของ Unit test ที่ใช้งานบ่อยๆ

NoError

คือ Assert ที่ใช้สำหรับตรวจสอบ Function ของเราซึ่งปกติ Function ที่เราเขียนจะมีการ return errors ออกมาด้วยเมื่อเราเขียนใช้งาน Function แล้วถ้าเริ่มใส่ Assert Noerror ถ้าหาก error ที่ return ออกมาเป็น nil ก็จะถือว่าผ่าน

func (suite *PackageTestSuite) TestCreate() {
givenInput := companyin.MakeTestCreateInput()
givenCompany := suite.makeDataTestCompany

suite.validator.On("Validate", givenInput).Once().Return(nil)
suite.repo.On("Create", mock.Anything, givenCompany).Once().Return(givenCompany.ID, nil)
actualID, err := suite.service.Create(suite.ctx, givenInput)
suite.NoError(err)
}

Error

คือ Assert ที่ใช้สำหรับตรวจสอบ Function ของเราซึ่งปกติ Function ที่เราเขียนจะมีการ return errors ออกมาด้วยเมื่อเราเขียนใช้งาน Function แล้วถ้าเริ่มใส่ Assert error ถ้าหาก error ที่ return ออกมาเป็นค่า Error ตามที่เราเขียน เงื่อนไขไว้ก็จะถือว่าผ่าน

func (suite *PackageTestSuite) TestCreateFail() {
expectedErr := errors.New("Test")
suite.Error(expectedErr)
}

NotEqual

คือ Assert ที่ใช้สำหรับตรวจสอบ param ต่างๆหรือส่วนที่ ผ่าน mechanism ต่างๆ เพื่อตรวจสอบว่าตรงกับ condition ที่เราคิดไว้หรือไม่ Assert ตัวนี้คือ ไม่เท่ากับ

func (suite *PackageTestSuite) TestCreate() {
input := companyin.MakeTestCreateInput()
req, resp, err := suite.makeCreateReq(input)
suite.NoError(err)

newID := "test"
suite.NotEqual(newID, "test1")
}

Equal

คือ Assert ที่ใช้สำหรับตรวจสอบ param ต่างๆหรือส่วนที่ ผ่าน mechanism ต่างๆ เพื่อตรวจสอบว่าตรงกับ condition ที่เราคิดไว้หรือไม่ Assert ตัวนี้คือ เท่ากับ

func (suite *PackageTestSuite) TestCreate() {
givenInput := companyin.MakeTestCreateInput()
givenCompany := suite.makeDataTestCompany

suite.validator.On("Validate", givenInput).Once().Return(nil)
suite.repo.On("Create", mock.Anything, givenCompany).Once().Return(givenCompany.ID, nil)
actualID, err := suite.service.Create(suite.ctx, givenInput)
suite.Equal(givenCompany.ID, actualID)
}

NotEmpty

คือ Assert ที่ใช้สำหรับตรวจสอบ param ต่างๆหรือส่วนที่ ผ่าน mechanism ต่างๆ เพื่อตรวจสอบว่าตรงกับ condition ที่เราคิดไว้หรือไม่ Assert ตัวนี้คือ ไม่เท่ากับค่าว่าง

func (suite *PackageTestSuite) TestCreate() {
input := companyin.MakeTestCreateInput()
req, resp, err := suite.makeCreateReq(input)
suite.NoError(err)

newID := "test"
suite.NotEmpty(newID)
}

AssertExpectations

คือ Assert ที่ใช้สำหรับตรวจสอบค่าว่าใน Mock มีการส่งค่ามาถูกต้องหรือไม่

func (suite *PackageTestSuite) TestCreate() {
input := companyin.MakeTestCreateInput()
req, resp, err := suite.makeCreateReq(input)
suite.NoError(err)

newID := "test"
suite.service.On("Create", mock.Anything, input).Return(newID, nil)
suite.router.ServeHTTP(resp, req)
suite.service.AssertExpectations(suite.T())
suite.Equal(http.StatusCreated, resp.Code)
suite.Equal(newID, resp.Header().Get("Content-Location"))
}

--

--