How to not write a boring testHow to not write a boring testHow to not write a boring test Dan TranDan TranDan Tran
Development and TestingDevelopment and TestingDevelopment and Testing 222
Test Driven DevelopmentTest Driven DevelopmentTest Driven Development Write testWrite testWrite test Write codeWrite codeWrite code 333
Unit testingUnit testingUnit testing Table-driven testTable-driven testTable-driven test StubStubStub MockingMockingMocking 444
Table-driven testTable-driven testTable-driven test // Gcd returns the greatest common divisor of 2 numbers.// Gcd returns the greatest common divisor of 2 numbers.// Gcd returns the greatest common divisor of 2 numbers. func Gcd(a, b int) int {func Gcd(a, b int) int {func Gcd(a, b int) int { if a%2 == 0 {if a%2 == 0 {if a%2 == 0 { if b%2 == 0 {if b%2 == 0 {if b%2 == 0 { return 2 * Gcd(a/2, b/2)return 2 * Gcd(a/2, b/2)return 2 * Gcd(a/2, b/2) }}} return Gcd(a/2, b)return Gcd(a/2, b)return Gcd(a/2, b) }}} if b%2 == 0 {if b%2 == 0 {if b%2 == 0 { return Gcd(a, b/2)return Gcd(a, b/2)return Gcd(a, b/2) }}} if a == b {if a == b {if a == b { return areturn areturn a }}} if a > b {if a > b {if a > b { return Gcd(a-b, b)return Gcd(a-b, b)return Gcd(a-b, b) }}} return Gcd(b-a, a)return Gcd(b-a, a)return Gcd(b-a, a) }}} Run 555
Table-driven test (2)Table-driven test (2)Table-driven test (2) func TestGcd(t *testing.T) {func TestGcd(t *testing.T) {func TestGcd(t *testing.T) { tests := []struct {tests := []struct {tests := []struct { name stringname stringname string args argsargs argsargs args want intwant intwant int }{}{}{ {{{ "Test two different numbers","Test two different numbers","Test two different numbers", args{a: 4, b: 6},args{a: 4, b: 6},args{a: 4, b: 6}, 2,2,2, },},}, {{{ "Test two identical numbers","Test two identical numbers","Test two identical numbers", args{a: 5, b: 5},args{a: 5, b: 5},args{a: 5, b: 5}, 5,5,5, },},}, }}} for _, tt := range tests {for _, tt := range tests {for _, tt := range tests { tt := tttt := tttt := tt t.Run(tt.name, func(t *testing.T) {t.Run(tt.name, func(t *testing.T) {t.Run(tt.name, func(t *testing.T) { if got := Gcd(tt.args.a, tt.args.b); got != tt.want {if got := Gcd(tt.args.a, tt.args.b); got != tt.want {if got := Gcd(tt.args.a, tt.args.b); got != tt.want { t.Errorf("Gcd() = %v, want %v", got, tt.want)t.Errorf("Gcd() = %v, want %v", got, tt.want)t.Errorf("Gcd() = %v, want %v", got, tt.want) }}} })})}) }}} }}} Run
666
ExampleExampleExample package stripepackage stripepackage stripe // Client ...// Client ...// Client ... // go:generate mockery -name=Client -case=underscore// go:generate mockery -name=Client -case=underscore// go:generate mockery -name=Client -case=underscore type Client interface {type Client interface {type Client interface { GetBalance(customerID string, currency string) (int64, error)GetBalance(customerID string, currency string) (int64, error)GetBalance(customerID string, currency string) (int64, error) Charge(customerID string, amount int64, currency string, desc string) errorCharge(customerID string, amount int64, currency string, desc string) errorCharge(customerID string, amount int64, currency string, desc string) error }}} Run 777
Stub (1)Stub (1)Stub (1) Create an implementation which has the same set of functions as the mainCreate an implementation which has the same set of functions as the mainCreate an implementation which has the same set of functions as the main object/class.object/class.object/class. type stubClient struct{}type stubClient struct{}type stubClient struct{} // GetBalance ...// GetBalance ...// GetBalance ... func (c *stubClient) GetBalance(customerID string, currency string) (int64, error) {func (c *stubClient) GetBalance(customerID string, currency string) (int64, error) {func (c *stubClient) GetBalance(customerID string, currency string) (int64, error) { return 100, nilreturn 100, nilreturn 100, nil }}} // Charge ...// Charge ...// Charge ... func (c *stubClient) Charge(customerID string, amount int64, currency string, desc string) errorfunc (c *stubClient) Charge(customerID string, amount int64, currency string, desc string) errorfunc (c *stubClient) Charge(customerID string, amount int64, currency string, desc string) error return nilreturn nilreturn nil }}} // TestGetBalanceHappyPath// TestGetBalanceHappyPath// TestGetBalanceHappyPath Run 888
Stub (2)Stub (2)Stub (2) func TestGetBalanceHappyPath(t *testing.T) {func TestGetBalanceHappyPath(t *testing.T) {func TestGetBalanceHappyPath(t *testing.T) { stripeClient = &stubClient{}stripeClient = &stubClient{}stripeClient = &stubClient{} t.Run("Happy path", func(t *testing.T) {t.Run("Happy path", func(t *testing.T) {t.Run("Happy path", func(t *testing.T) { got, err := GetBalance(existingCustomerID)got, err := GetBalance(existingCustomerID)got, err := GetBalance(existingCustomerID) if err != nil {if err != nil {if err != nil { t.Errorf("GetBalance() error = %v, wantErr %v", err, nil)t.Errorf("GetBalance() error = %v, wantErr %v", err, nil)t.Errorf("GetBalance() error = %v, wantErr %v", err, nil) returnreturnreturn }}} if got != 100 {if got != 100 {if got != 100 { t.Errorf("GetBalance() = %v, want %v", got, 100)t.Errorf("GetBalance() = %v, want %v", got, 100)t.Errorf("GetBalance() = %v, want %v", got, 100) }}} })})}) }}} Run 999
Generate mockGenerate mockGenerate mock Using mockery: go get github.com/vektra/mockery/...Using mockery: go get github.com/vektra/mockery/...Using mockery: go get github.com/vektra/mockery/... mockery -name=Client -case=underscoremockery -name=Client -case=underscoremockery -name=Client -case=underscore In test leIn test leIn test le m := mocks.Client{}m := mocks.Client{}m := mocks.Client{} m.On("GetBalance", mock.AnythingOfType("string"), mock.AnythingOfType("string")).m.On("GetBalance", mock.AnythingOfType("string"), mock.AnythingOfType("string")).m.On("GetBalance", mock.AnythingOfType("string"), mock.AnythingOfType("string")). Return(int64(100), nil).Once()Return(int64(100), nil).Once()Return(int64(100), nil).Once() 101010
MockingMockingMocking for _, scenario := range tests {for _, scenario := range tests {for _, scenario := range tests { scenario := scenarioscenario := scenarioscenario := scenario t.Run(scenario.name, func(t *testing.T) {t.Run(scenario.name, func(t *testing.T) {t.Run(scenario.name, func(t *testing.T) { m := &mocks.Client{}m := &mocks.Client{}m := &mocks.Client{} scenario.configureMock(m)scenario.configureMock(m)scenario.configureMock(m) stripeClient = mstripeClient = mstripeClient = m got, err := GetBalance(scenario.userID)got, err := GetBalance(scenario.userID)got, err := GetBalance(scenario.userID) if (err != nil) != scenario.wantErr {if (err != nil) != scenario.wantErr {if (err != nil) != scenario.wantErr { t.Errorf("GetBalance() error = %v, wantErr %v", err, scenario.wantErr)t.Errorf("GetBalance() error = %v, wantErr %v", err, scenario.wantErr)t.Errorf("GetBalance() error = %v, wantErr %v", err, scenario.wantErr) returnreturnreturn }}} if got != scenario.want {if got != scenario.want {if got != scenario.want { t.Errorf("GetBalance() = %v, want %v", got, scenario.want)t.Errorf("GetBalance() = %v, want %v", got, scenario.want)t.Errorf("GetBalance() = %v, want %v", got, scenario.want) returnreturnreturn }}} })})}) }}} }}} Run 111111
Dependency InjectionDependency InjectionDependency Injection 121212
Monkey patchingMonkey patchingMonkey patching In api.goIn api.goIn api.go In api_test.goIn api_test.goIn api_test.go originalFn := getBalanceoriginalFn := getBalanceoriginalFn := getBalance getBalance = func(string, string)(int, error){...}getBalance = func(string, string)(int, error){...}getBalance = func(string, string)(int, error){...} defer func() {defer func() {defer func() { getBalance = originalFngetBalance = originalFngetBalance = originalFn }()}()}() var getBalance = stripeClient.GetBalancevar getBalance = stripeClient.GetBalancevar getBalance = stripeClient.GetBalance // GetBalance2 ...// GetBalance2 ...// GetBalance2 ... func GetBalance2(userID string) (int64, error) {func GetBalance2(userID string) (int64, error) {func GetBalance2(userID string) (int64, error) { currency := loadCurrencyByUser(userID)currency := loadCurrencyByUser(userID)currency := loadCurrencyByUser(userID) bal, err := getBalance(userID, currency)bal, err := getBalance(userID, currency)bal, err := getBalance(userID, currency) if err != nil {if err != nil {if err != nil { return 0, errreturn 0, errreturn 0, err }}} return bal, nilreturn bal, nilreturn bal, nil }}} Run 131313
Behaviour Driven DevelopmentBehaviour Driven DevelopmentBehaviour Driven Development 141414
Cucumber for golangCucumber for golangCucumber for golang Feature: get versionFeature: get versionFeature: get version In order to know godog versionIn order to know godog versionIn order to know godog version As an API userAs an API userAs an API user I need to be able to request versionI need to be able to request versionI need to be able to request version Scenario: does not allow POST methodScenario: does not allow POST methodScenario: does not allow POST method When I send "POST" request to "/version"When I send "POST" request to "/version"When I send "POST" request to "/version" Then the response code should be 405Then the response code should be 405Then the response code should be 405 And the response should match json:And the response should match json:And the response should match json: """"""""" {{{ "error": "Method not allowed""error": "Method not allowed""error": "Method not allowed" }}} """"""""" Scenario: should get version numberScenario: should get version numberScenario: should get version number When I send "GET" request to "/version"When I send "GET" request to "/version"When I send "GET" request to "/version" Then the response code should be 200Then the response code should be 200Then the response code should be 200 And the response should match json:And the response should match json:And the response should match json: """"""""" {{{ "version": "v0.7.13""version": "v0.7.13""version": "v0.7.13" }}} """"""""" Run 151515
Cucumber style - GodogCucumber style - GodogCucumber style - Godog func FeatureContext(s *godog.Suite) {func FeatureContext(s *godog.Suite) {func FeatureContext(s *godog.Suite) { api := &apiFeature{}api := &apiFeature{}api := &apiFeature{} s.BeforeScenario(api.resetResponse)s.BeforeScenario(api.resetResponse)s.BeforeScenario(api.resetResponse) s.Step(`^I send "(GET|POST|PUT|DELETE)" request to "([^"]*)"$`, api.iSendrequestTo)s.Step(`^I send "(GET|POST|PUT|DELETE)" request to "([^"]*)"$`, api.iSendrequestTo)s.Step(`^I send "(GET|POST|PUT|DELETE)" request to "([^"]*)"$`, api.iSendrequestTo) s.Step(`^the response code should be (d+)$`, api.theResponseCodeShouldBe)s.Step(`^the response code should be (d+)$`, api.theResponseCodeShouldBe)s.Step(`^the response code should be (d+)$`, api.theResponseCodeShouldBe) s.Step(`^the response should match json:$`, api.theResponseShouldMatchJSON)s.Step(`^the response should match json:$`, api.theResponseShouldMatchJSON)s.Step(`^the response should match json:$`, api.theResponseShouldMatchJSON) }}} Run 161616
Other TipsOther TipsOther Tips Make a habit: de ne a package with interface and mockMake a habit: de ne a package with interface and mockMake a habit: de ne a package with interface and mock What I should test: functions or behaviours. Write happy path case rst.What I should test: functions or behaviours. Write happy path case rst.What I should test: functions or behaviours. Write happy path case rst. Consider writting tests for public funcs than private func.Consider writting tests for public funcs than private func.Consider writting tests for public funcs than private func. Create di erent build tag for unit and integration testsCreate di erent build tag for unit and integration testsCreate di erent build tag for unit and integration tests // +build unit// +build unit// +build unit // +build integration// +build integration// +build integration Disabling cache with count=1Disabling cache with count=1Disabling cache with count=1 171717
Test coverageTest coverageTest coverage go test -covermode=count -coverprofile=count.outgo test -covermode=count -coverprofile=count.outgo test -covermode=count -coverprofile=count.out go tool cover -func=count.outgo tool cover -func=count.outgo tool cover -func=count.out 181818
Using git pre-commit hookUsing git pre-commit hookUsing git pre-commit hook Edit local changes in .git/hooks/pre-commitEdit local changes in .git/hooks/pre-commitEdit local changes in .git/hooks/pre-commit Trigger go lint/test before every commitTrigger go lint/test before every commitTrigger go lint/test before every commit #!/bin/sh#!/bin/sh#!/bin/sh # run go lint# run go lint# run go lint # run unit tests# run unit tests# run unit tests go test -count=1 -short ./...go test -count=1 -short ./...go test -count=1 -short ./... if [[ $? == 1 ]]; thenif [[ $? == 1 ]]; thenif [[ $? == 1 ]]; then printf "unit test failed"printf "unit test failed"printf "unit test failed" exit 1exit 1exit 1 fififi # run integration tests# run integration tests# run integration tests # go test --tags=integration ./...# go test --tags=integration ./...# go test --tags=integration ./... exit 0exit 0exit 0 Run 191919
ResourcesResourcesResources Demo code:Demo code:Demo code: gitlab.com/aladine/testing-demogitlab.com/aladine/testing-demogitlab.com/aladine/testing-demo(https://gitlab.com/aladine/testing-demo)(https://gitlab.com/aladine/testing-demo)(https://gitlab.com/aladine/testing-demo) Hands-On Dependency Injection in Go, Corey Scott:Hands-On Dependency Injection in Go, Corey Scott:Hands-On Dependency Injection in Go, Corey Scott: EbookEbookEbook (https://subscription.packtpub.com/book/application_development/9781789132762)(https://subscription.packtpub.com/book/application_development/9781789132762)(https://subscription.packtpub.com/book/application_development/9781789132762) https://github.com/DATA-DOG/godoghttps://github.com/DATA-DOG/godoghttps://github.com/DATA-DOG/godog Arts icon byArts icon byArts icon by twitter.com/deniseyu21twitter.com/deniseyu21twitter.com/deniseyu21(https://twitter.com/deniseyu21)(https://twitter.com/deniseyu21)(https://twitter.com/deniseyu21) 202020
Hiring - OpenSimSimHiring - OpenSimSimHiring - OpenSimSim We are hiring golang developers and devops engineers.We are hiring golang developers and devops engineers.We are hiring golang developers and devops engineers. 212121
Thank youThank youThank you Dan TranDan TranDan Tran @trongdan_tran@trongdan_tran@trongdan_tran(http://twitter.com/trongdan_tran)(http://twitter.com/trongdan_tran)(http://twitter.com/trongdan_tran) Senior Software Developer atSenior Software Developer atSenior Software Developer at OpensimsimOpensimsimOpensimsim(https://opensimsim.com/)(https://opensimsim.com/)(https://opensimsim.com/)
How to not write a boring test in Golang

How to not write a boring test in Golang

  • 1.
    How to notwrite a boring testHow to not write a boring testHow to not write a boring test Dan TranDan TranDan Tran
  • 2.
    Development and TestingDevelopmentand TestingDevelopment and Testing 222
  • 3.
    Test Driven DevelopmentTestDriven DevelopmentTest Driven Development Write testWrite testWrite test Write codeWrite codeWrite code 333
  • 4.
    Unit testingUnit testingUnittesting Table-driven testTable-driven testTable-driven test StubStubStub MockingMockingMocking 444
  • 5.
    Table-driven testTable-driven testTable-driventest // Gcd returns the greatest common divisor of 2 numbers.// Gcd returns the greatest common divisor of 2 numbers.// Gcd returns the greatest common divisor of 2 numbers. func Gcd(a, b int) int {func Gcd(a, b int) int {func Gcd(a, b int) int { if a%2 == 0 {if a%2 == 0 {if a%2 == 0 { if b%2 == 0 {if b%2 == 0 {if b%2 == 0 { return 2 * Gcd(a/2, b/2)return 2 * Gcd(a/2, b/2)return 2 * Gcd(a/2, b/2) }}} return Gcd(a/2, b)return Gcd(a/2, b)return Gcd(a/2, b) }}} if b%2 == 0 {if b%2 == 0 {if b%2 == 0 { return Gcd(a, b/2)return Gcd(a, b/2)return Gcd(a, b/2) }}} if a == b {if a == b {if a == b { return areturn areturn a }}} if a > b {if a > b {if a > b { return Gcd(a-b, b)return Gcd(a-b, b)return Gcd(a-b, b) }}} return Gcd(b-a, a)return Gcd(b-a, a)return Gcd(b-a, a) }}} Run 555
  • 6.
    Table-driven test (2)Table-driventest (2)Table-driven test (2) func TestGcd(t *testing.T) {func TestGcd(t *testing.T) {func TestGcd(t *testing.T) { tests := []struct {tests := []struct {tests := []struct { name stringname stringname string args argsargs argsargs args want intwant intwant int }{}{}{ {{{ "Test two different numbers","Test two different numbers","Test two different numbers", args{a: 4, b: 6},args{a: 4, b: 6},args{a: 4, b: 6}, 2,2,2, },},}, {{{ "Test two identical numbers","Test two identical numbers","Test two identical numbers", args{a: 5, b: 5},args{a: 5, b: 5},args{a: 5, b: 5}, 5,5,5, },},}, }}} for _, tt := range tests {for _, tt := range tests {for _, tt := range tests { tt := tttt := tttt := tt t.Run(tt.name, func(t *testing.T) {t.Run(tt.name, func(t *testing.T) {t.Run(tt.name, func(t *testing.T) { if got := Gcd(tt.args.a, tt.args.b); got != tt.want {if got := Gcd(tt.args.a, tt.args.b); got != tt.want {if got := Gcd(tt.args.a, tt.args.b); got != tt.want { t.Errorf("Gcd() = %v, want %v", got, tt.want)t.Errorf("Gcd() = %v, want %v", got, tt.want)t.Errorf("Gcd() = %v, want %v", got, tt.want) }}} })})}) }}} }}} Run
  • 7.
  • 8.
    ExampleExampleExample package stripepackage stripepackagestripe // Client ...// Client ...// Client ... // go:generate mockery -name=Client -case=underscore// go:generate mockery -name=Client -case=underscore// go:generate mockery -name=Client -case=underscore type Client interface {type Client interface {type Client interface { GetBalance(customerID string, currency string) (int64, error)GetBalance(customerID string, currency string) (int64, error)GetBalance(customerID string, currency string) (int64, error) Charge(customerID string, amount int64, currency string, desc string) errorCharge(customerID string, amount int64, currency string, desc string) errorCharge(customerID string, amount int64, currency string, desc string) error }}} Run 777
  • 9.
    Stub (1)Stub (1)Stub(1) Create an implementation which has the same set of functions as the mainCreate an implementation which has the same set of functions as the mainCreate an implementation which has the same set of functions as the main object/class.object/class.object/class. type stubClient struct{}type stubClient struct{}type stubClient struct{} // GetBalance ...// GetBalance ...// GetBalance ... func (c *stubClient) GetBalance(customerID string, currency string) (int64, error) {func (c *stubClient) GetBalance(customerID string, currency string) (int64, error) {func (c *stubClient) GetBalance(customerID string, currency string) (int64, error) { return 100, nilreturn 100, nilreturn 100, nil }}} // Charge ...// Charge ...// Charge ... func (c *stubClient) Charge(customerID string, amount int64, currency string, desc string) errorfunc (c *stubClient) Charge(customerID string, amount int64, currency string, desc string) errorfunc (c *stubClient) Charge(customerID string, amount int64, currency string, desc string) error return nilreturn nilreturn nil }}} // TestGetBalanceHappyPath// TestGetBalanceHappyPath// TestGetBalanceHappyPath Run 888
  • 10.
    Stub (2)Stub (2)Stub(2) func TestGetBalanceHappyPath(t *testing.T) {func TestGetBalanceHappyPath(t *testing.T) {func TestGetBalanceHappyPath(t *testing.T) { stripeClient = &stubClient{}stripeClient = &stubClient{}stripeClient = &stubClient{} t.Run("Happy path", func(t *testing.T) {t.Run("Happy path", func(t *testing.T) {t.Run("Happy path", func(t *testing.T) { got, err := GetBalance(existingCustomerID)got, err := GetBalance(existingCustomerID)got, err := GetBalance(existingCustomerID) if err != nil {if err != nil {if err != nil { t.Errorf("GetBalance() error = %v, wantErr %v", err, nil)t.Errorf("GetBalance() error = %v, wantErr %v", err, nil)t.Errorf("GetBalance() error = %v, wantErr %v", err, nil) returnreturnreturn }}} if got != 100 {if got != 100 {if got != 100 { t.Errorf("GetBalance() = %v, want %v", got, 100)t.Errorf("GetBalance() = %v, want %v", got, 100)t.Errorf("GetBalance() = %v, want %v", got, 100) }}} })})}) }}} Run 999
  • 11.
    Generate mockGenerate mockGeneratemock Using mockery: go get github.com/vektra/mockery/...Using mockery: go get github.com/vektra/mockery/...Using mockery: go get github.com/vektra/mockery/... mockery -name=Client -case=underscoremockery -name=Client -case=underscoremockery -name=Client -case=underscore In test leIn test leIn test le m := mocks.Client{}m := mocks.Client{}m := mocks.Client{} m.On("GetBalance", mock.AnythingOfType("string"), mock.AnythingOfType("string")).m.On("GetBalance", mock.AnythingOfType("string"), mock.AnythingOfType("string")).m.On("GetBalance", mock.AnythingOfType("string"), mock.AnythingOfType("string")). Return(int64(100), nil).Once()Return(int64(100), nil).Once()Return(int64(100), nil).Once() 101010
  • 12.
    MockingMockingMocking for _, scenario:= range tests {for _, scenario := range tests {for _, scenario := range tests { scenario := scenarioscenario := scenarioscenario := scenario t.Run(scenario.name, func(t *testing.T) {t.Run(scenario.name, func(t *testing.T) {t.Run(scenario.name, func(t *testing.T) { m := &mocks.Client{}m := &mocks.Client{}m := &mocks.Client{} scenario.configureMock(m)scenario.configureMock(m)scenario.configureMock(m) stripeClient = mstripeClient = mstripeClient = m got, err := GetBalance(scenario.userID)got, err := GetBalance(scenario.userID)got, err := GetBalance(scenario.userID) if (err != nil) != scenario.wantErr {if (err != nil) != scenario.wantErr {if (err != nil) != scenario.wantErr { t.Errorf("GetBalance() error = %v, wantErr %v", err, scenario.wantErr)t.Errorf("GetBalance() error = %v, wantErr %v", err, scenario.wantErr)t.Errorf("GetBalance() error = %v, wantErr %v", err, scenario.wantErr) returnreturnreturn }}} if got != scenario.want {if got != scenario.want {if got != scenario.want { t.Errorf("GetBalance() = %v, want %v", got, scenario.want)t.Errorf("GetBalance() = %v, want %v", got, scenario.want)t.Errorf("GetBalance() = %v, want %v", got, scenario.want) returnreturnreturn }}} })})}) }}} }}} Run 111111
  • 13.
  • 14.
    Monkey patchingMonkey patchingMonkeypatching In api.goIn api.goIn api.go In api_test.goIn api_test.goIn api_test.go originalFn := getBalanceoriginalFn := getBalanceoriginalFn := getBalance getBalance = func(string, string)(int, error){...}getBalance = func(string, string)(int, error){...}getBalance = func(string, string)(int, error){...} defer func() {defer func() {defer func() { getBalance = originalFngetBalance = originalFngetBalance = originalFn }()}()}() var getBalance = stripeClient.GetBalancevar getBalance = stripeClient.GetBalancevar getBalance = stripeClient.GetBalance // GetBalance2 ...// GetBalance2 ...// GetBalance2 ... func GetBalance2(userID string) (int64, error) {func GetBalance2(userID string) (int64, error) {func GetBalance2(userID string) (int64, error) { currency := loadCurrencyByUser(userID)currency := loadCurrencyByUser(userID)currency := loadCurrencyByUser(userID) bal, err := getBalance(userID, currency)bal, err := getBalance(userID, currency)bal, err := getBalance(userID, currency) if err != nil {if err != nil {if err != nil { return 0, errreturn 0, errreturn 0, err }}} return bal, nilreturn bal, nilreturn bal, nil }}} Run 131313
  • 15.
    Behaviour Driven DevelopmentBehaviourDriven DevelopmentBehaviour Driven Development 141414
  • 16.
    Cucumber for golangCucumberfor golangCucumber for golang Feature: get versionFeature: get versionFeature: get version In order to know godog versionIn order to know godog versionIn order to know godog version As an API userAs an API userAs an API user I need to be able to request versionI need to be able to request versionI need to be able to request version Scenario: does not allow POST methodScenario: does not allow POST methodScenario: does not allow POST method When I send "POST" request to "/version"When I send "POST" request to "/version"When I send "POST" request to "/version" Then the response code should be 405Then the response code should be 405Then the response code should be 405 And the response should match json:And the response should match json:And the response should match json: """"""""" {{{ "error": "Method not allowed""error": "Method not allowed""error": "Method not allowed" }}} """"""""" Scenario: should get version numberScenario: should get version numberScenario: should get version number When I send "GET" request to "/version"When I send "GET" request to "/version"When I send "GET" request to "/version" Then the response code should be 200Then the response code should be 200Then the response code should be 200 And the response should match json:And the response should match json:And the response should match json: """"""""" {{{ "version": "v0.7.13""version": "v0.7.13""version": "v0.7.13" }}} """"""""" Run 151515
  • 17.
    Cucumber style -GodogCucumber style - GodogCucumber style - Godog func FeatureContext(s *godog.Suite) {func FeatureContext(s *godog.Suite) {func FeatureContext(s *godog.Suite) { api := &apiFeature{}api := &apiFeature{}api := &apiFeature{} s.BeforeScenario(api.resetResponse)s.BeforeScenario(api.resetResponse)s.BeforeScenario(api.resetResponse) s.Step(`^I send "(GET|POST|PUT|DELETE)" request to "([^"]*)"$`, api.iSendrequestTo)s.Step(`^I send "(GET|POST|PUT|DELETE)" request to "([^"]*)"$`, api.iSendrequestTo)s.Step(`^I send "(GET|POST|PUT|DELETE)" request to "([^"]*)"$`, api.iSendrequestTo) s.Step(`^the response code should be (d+)$`, api.theResponseCodeShouldBe)s.Step(`^the response code should be (d+)$`, api.theResponseCodeShouldBe)s.Step(`^the response code should be (d+)$`, api.theResponseCodeShouldBe) s.Step(`^the response should match json:$`, api.theResponseShouldMatchJSON)s.Step(`^the response should match json:$`, api.theResponseShouldMatchJSON)s.Step(`^the response should match json:$`, api.theResponseShouldMatchJSON) }}} Run 161616
  • 18.
    Other TipsOther TipsOtherTips Make a habit: de ne a package with interface and mockMake a habit: de ne a package with interface and mockMake a habit: de ne a package with interface and mock What I should test: functions or behaviours. Write happy path case rst.What I should test: functions or behaviours. Write happy path case rst.What I should test: functions or behaviours. Write happy path case rst. Consider writting tests for public funcs than private func.Consider writting tests for public funcs than private func.Consider writting tests for public funcs than private func. Create di erent build tag for unit and integration testsCreate di erent build tag for unit and integration testsCreate di erent build tag for unit and integration tests // +build unit// +build unit// +build unit // +build integration// +build integration// +build integration Disabling cache with count=1Disabling cache with count=1Disabling cache with count=1 171717
  • 19.
    Test coverageTest coverageTestcoverage go test -covermode=count -coverprofile=count.outgo test -covermode=count -coverprofile=count.outgo test -covermode=count -coverprofile=count.out go tool cover -func=count.outgo tool cover -func=count.outgo tool cover -func=count.out 181818
  • 20.
    Using git pre-commithookUsing git pre-commit hookUsing git pre-commit hook Edit local changes in .git/hooks/pre-commitEdit local changes in .git/hooks/pre-commitEdit local changes in .git/hooks/pre-commit Trigger go lint/test before every commitTrigger go lint/test before every commitTrigger go lint/test before every commit #!/bin/sh#!/bin/sh#!/bin/sh # run go lint# run go lint# run go lint # run unit tests# run unit tests# run unit tests go test -count=1 -short ./...go test -count=1 -short ./...go test -count=1 -short ./... if [[ $? == 1 ]]; thenif [[ $? == 1 ]]; thenif [[ $? == 1 ]]; then printf "unit test failed"printf "unit test failed"printf "unit test failed" exit 1exit 1exit 1 fififi # run integration tests# run integration tests# run integration tests # go test --tags=integration ./...# go test --tags=integration ./...# go test --tags=integration ./... exit 0exit 0exit 0 Run 191919
  • 21.
    ResourcesResourcesResources Demo code:Demo code:Democode: gitlab.com/aladine/testing-demogitlab.com/aladine/testing-demogitlab.com/aladine/testing-demo(https://gitlab.com/aladine/testing-demo)(https://gitlab.com/aladine/testing-demo)(https://gitlab.com/aladine/testing-demo) Hands-On Dependency Injection in Go, Corey Scott:Hands-On Dependency Injection in Go, Corey Scott:Hands-On Dependency Injection in Go, Corey Scott: EbookEbookEbook (https://subscription.packtpub.com/book/application_development/9781789132762)(https://subscription.packtpub.com/book/application_development/9781789132762)(https://subscription.packtpub.com/book/application_development/9781789132762) https://github.com/DATA-DOG/godoghttps://github.com/DATA-DOG/godoghttps://github.com/DATA-DOG/godog Arts icon byArts icon byArts icon by twitter.com/deniseyu21twitter.com/deniseyu21twitter.com/deniseyu21(https://twitter.com/deniseyu21)(https://twitter.com/deniseyu21)(https://twitter.com/deniseyu21) 202020
  • 22.
    Hiring - OpenSimSimHiring- OpenSimSimHiring - OpenSimSim We are hiring golang developers and devops engineers.We are hiring golang developers and devops engineers.We are hiring golang developers and devops engineers. 212121
  • 23.
    Thank youThank youThankyou Dan TranDan TranDan Tran @trongdan_tran@trongdan_tran@trongdan_tran(http://twitter.com/trongdan_tran)(http://twitter.com/trongdan_tran)(http://twitter.com/trongdan_tran) Senior Software Developer atSenior Software Developer atSenior Software Developer at OpensimsimOpensimsimOpensimsim(https://opensimsim.com/)(https://opensimsim.com/)(https://opensimsim.com/)