Successfully reported this slideshow.
Your SlideShare is downloading. ×

Jaap Groeneveld - Go Unit Testing Workshop

Ad
Ad
Ad
Ad
Ad
Ad
Ad
Ad
Ad
Ad
Ad
Upcoming SlideShare
Golang dot-testing-lite
Golang dot-testing-lite
Loading in …3
×

Check these out next

1 of 43 Ad

More Related Content

Similar to Jaap Groeneveld - Go Unit Testing Workshop (20)

Recently uploaded (20)

Advertisement

Jaap Groeneveld - Go Unit Testing Workshop

  1. 1. TestinginGo JaapGroeneveld-jGroeneveld.de
  2. 2. Agenda Whydowetest Typesoftests Testdrivendevelopment TestinginGo 2
  3. 3. Whydowetest Savetime Confidentinyourchanges Fasterfeedback Increasequality Preventregressions Communicateanddocument =>makeyourselfhappier 3
  4. 4. Typesoftests UnitTests(fast) IntegrationTests End-to-EndTests(comprehensive) SmokeTests ConsumerDrivenContractTests ... 4
  5. 5. UnitTests Focusonsmallparts Reallyfast Extensivetestcases Runbydeveloper Example:Sum(a,b) https://martinfowler.com/bliki/UnitTest.html 5
  6. 6. Integrationtests Dounitsworkcorrectlytogether? Lessextensivetestcases Runbydeveloper Example:Webhandler->logic->DB. 6
  7. 7. UnitTestvsintegrationtests https://martinfowler.com/bliki/UnitTest.html 7
  8. 8. End-To-Endtests Fullstacktestagainstrealsystem Externalboundariesmightbemocked(e.g. WireMock) Slow RunbyCIsystem Dependingonthesystem,youmightnot needthem. https://www.guru99.com/end-to-end-testing.html 8
  9. 9. TestPyramid 9
  10. 10. Testdrivendevelopment Red,Green,Refactor Onlyrefactorwhengreen Benefits Nountestedcode Confidentrefactoring Betterdesign(...othertalk) Learngowithtests 10
  11. 11. 11
  12. 12. GoTestBasics
  13. 13. Agenda GeneralTesting Whattotest Subtests Tabletests Mocks 13
  14. 14. Go'stestfunctionalities go test ./... picksuptestsdefinedin X_test.go files Xisusuallythenameofthecodefile sum.go => sum_test.go Testshavethesignature func TestX(t *testing.T) Xisusuallythenameoftheunit(functionorstruct) Testfailuresarereportedwith t.Error and t.Fatal Youcanalsorunbenchmarkswith func Benchmark*(b *testing.B) appendix YoucanalsorundefineExamplesfordocumentation func Example*() appendix 14
  15. 15. Asimpletest func Sum(elements []int) int func TestSum(t *testing.T) { input := []int{1, 2, 3} expected := 6 actual := Sum(input) if actual != expected { t.Errorf("expected %v but was %v", expected, actual) } } 15
  16. 16. CleanCode-Tests Oneassertpertest. Readable. Fast. Independent. Repeatable. (...othertalk) 16
  17. 17. 1.Arrange 2.Act 3.Assert 17
  18. 18. Acleanersimpletest func TestSum(t *testing.T) { // Arrange input := []int{1, 2, 3} expected := 6 // Act actual := Sum(input) // Assert if actual != expected { t.Errorf("expected %v but was %v", expected, actual) } } 18
  19. 19. Yourturn! Startanewproject,writesimpletestandletthetestguideyoutoimplementthe method. 19
  20. 20. TestFrameworks gotest expected := "bar" actual, err := Foo() if err != nil { t.Fatalf("got error %v", err) } if expected != actual { t.Errorf("expected %v but got %v", expected, actual) } testify actual, err := Foo() require.Nil(t, err) assert.Equal(t, "bar", actual) 20
  21. 21. TestFrameworks Comparisonofgotestingframeworks(Testify,gocheck,gopwt,Ginkgo...) https://github.com/bmuschko/go-testing-frameworks 21
  22. 22. Whattotest Commoncase(Happypath) Edgecases Errorcases 22
  23. 23. Subtests Letyouorganizeyourteststocommunicatetheintentionmoreclearly func TestSum(t *testing.T) { t.Run("returns the summed up elements", func(t *testing.T) { // []...] }) t.Run("returns zero for empty array", func(t *testing.T) { // []...] }) } 23
  24. 24. Tablebasedtests Inaperfectworld,functionsjustrelyoninputandoutput.Thisiseasytotest. func TestSum(t *testing.T) { testCases := []struct { Name string Input []int Expected int }{ { Name: "returns the summed up elements", Input: []int{1, 2, 3}, Expected: 6, }, { Name: "returns zero for empty array", Input: []int{}. Expected: 0, }, } for _, tc := range testCases { t.Run(tc.Name, func(t *testing.T) { assert.Equal(t, tc.Expected, Sum(tc.input)) }) } } 24
  25. 25. generateNodeName CodeReview 25
  26. 26. TestDoubles 26
  27. 27. Testdoubles Fakes:Workingimplementation Stubs:Holdpredefineddata Mocks:Registercallstheyreceiveanddonothing Spies:Registercallstorealobjects 27
  28. 28. Whyusedoubles Toreallytest"units"andnotthedependencies Performance-Databases,Network... Collaboration-Implementcodeagainstdependenciesthatdonotexist,yet. 28
  29. 29. SimpleMocks:Code Wehaveafunction SendMail thatformatsamessageandusesthe MailSender to senditout. Sendingmailsisslowandwejustwanttoknowthatitwouldhappenwiththecorrect message. func SendMail(sender MailSender, recipient string) { sender.Send("Good morning, " + recipient) } type MailSender interface { Send(message string) } 29
  30. 30. SimpleMocks:Test func TestSendMail(t *testing.T) { calledWith := "" sender := MockMailSender{ SendFunc: func(message string) { calledWith = message }, } SendMail(sender, "Tyrion") if calledWith != "Good morning, Tyrion" { t.Errorf("Was called with %q", calledWith) } } 30
  31. 31. SimpleMocks:Mock type MockMailSender struct { SendFunc func(message string) } func (s MockMailSender) Send(message string) { s.SendFunc(message) } 31
  32. 32. MockingFrameworks gomockusescodegenerationtoprovidemocks mockgen -source=mail_sender.go func TestFoo(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() sender := NewMockMailSender(ctrl) sender. EXPECT(). Send("Good morning, Tyrion") SendMail(sender, "Tyrion") } 32
  33. 33. 33
  34. 34. Randomthings 34
  35. 35. Writinghelpers func equals(t *testing.T, a string, b string) { t.Helper() if a != b { t.Errorf("%q is not %q", a, b) } } 35
  36. 36. httptest.NewRecorder func TestGetPlayers(t *testing.T) { playerStore := &StubPlayerStore{map[string]int{ "Pepper": 20, }} server := PlayerServer{playerStore} request, _ := http.NewRequest(http.MethodGet, "/scores/pepper", nil) response := httptest.NewRecorder() server.ServeHTTP(response, request) assert.Equal(t, http.StatusOK, response.Code) assert.Equal(t, "20", response.Body.String()) } Useashttp.ResponseWritertotestyourownhandlers 36
  37. 37. httptest.NewServer server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) })) defer server.Close() // pass server.URL somewhere Greattofakerealapisforintegrationtesting. AlsoseeWireMockmockservertoconfigurearealserverifyouwanttotestthe infrastructure. 37
  38. 38. TestingJSONresponses(schema) func TestJSON(t *testing.T) { reader := getJSONResponse() err := schema.MatchJSON( schema.Map{ "id": schema.IsInteger, "name": "Max Mustermann", "age": 42, "height": schema.IsFloat, "footsize": schema.IsPresent, "address": schema.Map{ "street": schema.IsString, "zip": schema.IsString, }, "tags": schema.ArrayIncluding("red"), }, reader, ) } https://github.com/jgroeneveld/schema 38
  39. 39. Examples package stringutil_test import ( "fmt" "github.com/golang/example/stringutil" ) func ExampleReverse() { fmt.Println(stringutil.Reverse("hello")) // Output: olleh } Examplesrunliketests,soweknow theyarecorrect,andgenerate documentation. 39
  40. 40. Benchmarks func BenchmarkFib10(b *testing.B) { // run the Fib function b.N times for n := 0; n < b.N; n++ { Fib(10) } } % go test -bench=. PASS BenchmarkFib10 5000000 509 ns/op ok github.com/davecheney/fib 3.084s 40
  41. 41. Setup/Teardown func TestFoo() { var teardown = setup() defer teardown() // ... } func setup() func() { fmt.Println("this runs before test") return func() { fmt.Println("this runs after test") } } Greatifyouneedtodosomethingbeforetestsrunandalsocleanupafterwards e.g.disablelogging,prepare&cleandatabase,set/resetENV. 41
  42. 42. Globalhooks Youcanplacea func init() inthepackage_testtorunbeforethetestsrun. Better:InGo1.14wehavetheTestMain func TestMain(m *testing.M) { // call flag.Parse() here if TestMain uses flags fmt.Println("this runs before test") exitCode := m.Run() fmt.Println("this runs after test") os.Exit(exitCode) } 42
  43. 43. quick.Checkforpropertytesting Usethequickpackagetotestpropertieswithrandominputs func TestPropertiesOfConversion(t *testing.T) { t.Run("Conversion to roman and back to arabic yields initial input", func(t *testing.T) { assertion := func(arabic int) bool { roman := ConvertToRoman(arabic) fromRoman := ConvertToArabic(roman) return fromRoman == arabic } if err := quick.Check(assertion, nil); err != nil { t.Error("failed checks", err) } }) } 43

×