カバレッジ95%以上を実現した テストコードの書き方

D68ec2463959a924ada156a278743228?s=47 yudppp
April 21, 2018

カバレッジ95%以上を実現した テストコードの書き方

Battle Conference U30 ( https://bcu30.jp/ )で話した時の資料です

より安心して使っていただくために、これまでどうしても超えられなかった95%の壁を越えるための挑戦についてと今後のテスト方針について

D68ec2463959a924ada156a278743228?s=128

yudppp

April 21, 2018
Tweet

Transcript

  1. ΧόϨοδ95%Ҏ্Λ࣮ݱͨ͠ ςετίʔυͷॻ͖ํ גࣜձࣾ)3#SBJO
 ླ໦༔େ

  2. ࣗݾ঺հ w ླ໦༔େ w גࣜձࣾ)3#SBJOॴଐ w ޷͖ͳݴ༿ႈ౳ੑ w (P+BWB4DSJQU

  3. -ैۀһͷ໨ඪΛ؅ཧͯ͠ ɹɹɹɹධՁ͢ΔͨΊͷΫϥ΢υπʔϧ- https://okr.hrbrain.biz/

  4. ૣ଎Ͱ͕͢ɺ

  5. Έͳ͞Μςετ͸޷͖Ͱ͔͢?

  6. ࢲ͸ݏ͍Ͱ͢

  7. ςετΛݏ͍ͳཧ༝ w ΊΜͲ͍͘͞ w ଞਓͷॻ͍ͨςετίʔυΛϝϯςφϯε͢ΔͷͭΒ͍ w ࣮ߦ͢Δͷʹ͕͔͔࣌ؒΔ

  8. ࠓ·ͰͷγεςϜͱςετίʔυ

  9. γεςϜ֓ཁ w (P w 3%# w .7$෩ͷΞʔΩςΫνϟ

  10. DBʹ௚઀ΞΫηε͢Δςετ(ControllerͰ΋) func TestXxx(t *testing.T) { // DBʹςετσʔλΛηοτ͢Δ // ͍͔ͭ͘ͷςετέʔεͰςετ͢Δ //

    ςετσʔλͷ࡟আ }
  11. ಉ࣌ʹ࣮ߦ͢Δͱࣦഊ͢Δςετ w ςετσʔλ͕ڝ߹͠λΠϛϯάʹΑͬͯ͸༧ظͤ͵σʔλ͕ฦ Γςετ͕ࣦഊ͢Δɻɻ

  12. ղܾํ๏ go test -parallel 1 ./...

  13. ଞͷਓ͕ॻ͍ͨςετ w ਆݒ͔ΓతͳόϥϯεͰ੒Γཱ͍ͬͯΔ w $POUSPMMFSʹۙͮ͘΄Ͳෳࡶ͞͸૿͢ w ΧόϨοδΛݟΔݶΓςετࣗମ͸໰୊ͳ͘͞Ε͍ͯͦ͏ w ผػೳ͕ϦϦʔε͞Εͨͱ͖ʹσάϨ͞Ε͍ͯͳ͍͜ͱͷ֬ೝ͸ Ͱ͖͍ͯΔ͕ɺؔ܎ͨ͠ػೳΛϦϦʔε͍ͨ͠ͱ͖ʹਏ͍ɻ

  14. ౰࣌ͷϥΠϯΧόϨοδ w ϛυϧ΢ΣΞͳͲେࣄͳͱ͜ΖͰ΋ఔ౓ w શମݟΔͱΛ੾͍ͬͯͨ w ΧόϨοδΛ্͛Α͏ͱࢥͬͯ΋େมͳঢ়ଶ

  15. मਖ਼ޙ (Ұ෦·࣮ͩ૷தͷ΋ͷ͕͋Γ·͢)

  16. αʔόʔฤ

  17. ࣮૷࣌ͷϧʔϧΛ͍͔ͭ͘࡞Γ·ͨ͠

  18. ΫϦʔϯΞʔΩςΫνϟ https://8thlight.com/blog/uncle-bob/2012/08/13/the-clean-architecture.html

  19. ΫϦʔϯΞʔΩςΫνϟ w ؔ৺ͷ෼཭ w *OUFSGBDFͷಠཱ w σʔλϕʔεͷಠཱ w ֎෦ػೳͷಠཱ

  20. σΟϨΫτϦߏ੒ . ├── application │ └── usecase - usecase ├──

    domain │ ├── entity - domain entity(simple struct only) │ ├── repository - domain repository(interface only) │ └── service - domain service ├── infrastructure │ └── persistence │ └── rdb - implement domain repository └── interfaces └── api ├── internal │ ├── handler - http handler │ ├── http_utils - http utils │ └── middleware - http server middleware └── server.go - http server endpoint
  21. DI͢Δ type UserRepository interface { GetByID(ctx context.Context, id uint64) (*User,

    error) } 
 type userService struct { repo UserRepository } func NewUserService(repo UserRepository) UserService { return &userService{ repo: repo, } }
 
 func (u *userService) GetByID(ctx context.Context, id uint64) (*User, error) { … }
  22. ϞοΫԽͯ͠ςετ import ( "context" "testing" "github.com/golang/mock/gomock" ... ) func TestUserServiceGetByID(t

    *testing.T) { ... ctrl := gomock.NewController(t) defer ctrl.Finish() mockRepo := mock.NewMockUserRepository(ctrl) mockRepo.EXPECT().GetByID(c.context, c.id).Return(c.mockuser, nil) service := NewUserService(mockRepo) actual, err := GetByID(c.context, c.id) ... }
  23. SQL΋ϞοΫԽ import ( "regexp"
 sqlmock "gopkg.in/DATA-DOG/go-sqlmock.v1" ) func TestGetByID(t *testing.T)

    { … query := `SELECT …` rows := sqlmock.NewRows(columns) rows = rows.AddRow(expextRow…) mock.ExpectQuery(query).WillReturnRows(rows)
 repo := NewUserRepository(mockDB) actual, err := repo.GetByID(c.context, c.id) … }
  24. TableDrivenTests func TestSquare(t *testing.T) { cases := []struct{ expect int

    input int
 }{{1,1},{4,2}} for _, c := range cases { actual := Square(c.input) if actual != c.expect { t.Errorf("Square(%q) == %q, want %q",
 c. input, actual, c.expect, ) } } }
  25. ͲͪΒͷํ͕ྑ͍ίʔυͰ͠ΐ͏͔ if (A || B || C) { return errors.New("something")

    } return nil if (A) { return errors.New("something1") } if (B) { return errors.New("something2") } if (C) { return errors.New("something3") } return nil
  26. ӈΛྑ͍ͱ͍ͯ͠·͢ if (A || B || C) { return errors.New("something")

    } return nil if (A) { return errors.New("something1") } if (B) { return errors.New("something2") } if (C) { return errors.New("something3") } return nil
  27. ϥΠϯΧόϨοδͩͱͲͷέʔε͕ςετ ͞Ε͍ͯΔ͔Θ͔Βͳ͍ ͦ΋ͦ΋Τϥʔ͸෼͚Δ΂͖

  28. ϥΠϯΧόϨοδ $ go test -cover `go list ./... | grep

    -v mock` ok .../application/usecase coverage: 97.8% of statements ? .../domain/entity [no test files] ? .../domain/repository [no test files] ok .../domain/service coverage: 96.2% of statements ok …/infrastructure/persistence/rdb coverage: 100.0% of statements ok .../interfaces/api/internal/handler coverage: 94.7% of statements ok .../interfaces/api/internal/middleware coverage: 95.1% of statements …
  29. ࢒Γͷ5%ʹ͍ͭͯ cookie, err := r.Cookie("cookie_name") if err != nil {

    if err == http.ErrNoCookie { h.ServeHTTP(w, r) return } w.WriteHeader(http.StatusInternalServerError) return }
  30. ςετΛݏ͍ͳཧ༝ w ΊΜͲ͍͘͞
 ˠΧόϨοδΛ͋͛Δୡ੒ײΛָ͍͠ͱࢥ͍͜·ͤΔɻ w ଞਓͷॻ͍ͨςετίʔυΛϝϯςφϯε͢ΔͷͭΒ͍
 ˠϞοΫԽ͢Δ͜ͱͰγϯϓϧͳςετίʔυ΁ w ࣮ߦ͢Δͷʹ͕͔͔࣌ؒΔ
 ˠίΞ਺෼ฒྻʹ࣮ߦͰ͖ΔΑ͏ʹͳͬͨɻ


    ɹʴ3%#ʹ઀ଓ͠ͳ͍෼ଟগ଎͘ͳͬͨɻ
  31. (͜ͷลΓ͔Βເͷ࿩)

  32. ϑϩϯτΤϯυฤ

  33. ී௨ʹϢχοτςετΛ ͍ͬͺ͍ॻ͖͍ͨ

  34. E2Eςετ

  35. ݱঢ় w εϓϨουγʔτͰ؅ཧ͞Ε͍ͯΔςετ߲໨ॻΛ΋ͱʹखಈͰ ςετ w ͲΜͲΜػೳ͕૿͖͑ͯͯɺςετ߲໨΋૿͍͖͑ͯखಈ͕ਏ͘ ͳ͖ͬͯͨ

  36. ࣗಈԽ͍ͨ͠ ?

  37. Puppeteer w $ISPNF%FW5PPMT։ൃνʔϜ͕ग़ͨ͠044 w )FBEMFTT$ISPNF w $ISPNFͷΈ͔͠ಈ͔ͳ͍ w /PEF+4Ͱಈ͘ w

    IUUQTHJUIVCDPN(PPHMF$ISPNFQVQQFUFFS w IUUQTUSZQVQQFUFFSBQQTQPUDPN
  38. Ͳ͏ͯ͠SeleniumΛ࢖Θͳ͍ͷ͔ w ಋೖ͕؆୯ w /PEF+4Ͱςετ͕ॻ͖΍͍͢ "1*͕Θ͔Γ΍͍͢  w ໨తͱͯ͠͸σάϨͷ๷ࢭ͕΄ͱΜͲͰ$ISPNF͚ͩͰ֬ೝͰ͖ Ε͹Α͍

    ܦݧ্σάϨͰ*&Ͱಈ͔ͳ͘ͳͬͨέʔε͸͋·Γͳ ͍
  39. ࣗಈԽ͕؆୯ʹͰ͖Δͱ͜ΖͷΈࣗಈԽ ͦͷ͏ͪશͯࣗಈԽ͍ͨ͠(ເ)͕ແཧ͸͠ͳ͍

  40. ੬ऑੑςετ

  41. ݱঢ় w ఆظతʹ֎෦ʹ੬ऑੑςετΛ࣮ࢪ w ಺෦Ͱ΋खಈͰ࣮ࢪ

  42. શͯͷϦϦʔε͝ͱʹ੬ऑੑςετ(ເ)

  43. VAddy w खܰͰߴ଎ͳΫϥ΢υܕ8FC੬ऑੑݕࠪπʔϧ w ༧Ί༻ҙͨ͠63-΍ύϥϝʔλΛݩʹ੬ऑੑݕࠪΛߦ͏ w 42-ΠϯδΣΫγϣϯ944ϦϞʔτϑΝΠϧΠϯΫϧʔδϣϯ ίϚϯυΠϯδΣΫγϣϯσΟϨΫτϦτϥόʔαϧ

  44. VAddy ༧Ί༻ҙͨ͠URL:
 - http://example.com/search.php?keyword1=foo&keyword2=bar 
 ΞΫηε͞ΕΔURL:
 - http://example.com/search.php?keyword1=foo&keyword2=ba - http://example.com/search.php?

    keyword1=foo<script>vaddy</script>&keyword2=bar ...
  45. ༧Ί༻ҙ͞ΕͨURL΍ύϥϝʔλ?

  46. ༻ҙ͠ͳͯ͘΋͍͍ΜͰ͢

  47. ςετ͢ΔURLͷऩू CI Application Proxy E2E

  48. ऩूͨ͠URLΛݩʹ੬ऑੑςετ CI Application ੬ऑੑ਍அ

  49. E2Eςετ͕ॻ͔Ε͍ͯΕ͹ CIͰ׬݁͞ΕΔ

  50. ࣗಈͰϦϦʔε͝ͱʹ੬ऑੑ਍அͰ͖Δͷ͸ ΍͹͍ͷͰͳΔ΂͘ૣ͘΍Γ͍ͨ

  51. ิࠤతͳ΋ͷͳͷͰ ࠓޙ΋֎෦ʹґཔͨ͠Γɺ ࣗ෼ͨͪͰ֬ೝͨ͠Γ͸͠·͢

  52. ·ͱΊ w 5FTUBCMFͳߏ੒ʹ͢΂͖ɻ w ·ͨ5FTUBCMFͳίʔυ͸ݟ΍͘͢ศར w 1VQQFUFFSͱ7"EEZΛ࢖ͬͯɺΑΓ҆৺ͳαʔϏε΁͍͖ͯ͠· ͢ɻ

  53. ͝੩ௌ͋Γ͕ͱ͏͍͟͝·ͨ͠ɻ