Slide 1

Slide 1 text

変更につよい ユニットテストの考え方 鈴木まー PHP勉強会

Slide 2

Slide 2 text

自己紹介 @suzuki_mar

Slide 3

Slide 3 text

鈴木まー(suzuki_mar) RubyでDDDや モジュラーモノリスなど のソフトウェアアーキテクチャ の導入、定着を している PHP/Laravelで仕事をできるように 転職活動中&スキルアップ中 直近の仕事 最近していること オブジェクト指向カンファレンスの動画を30ぽんぐらい公開した LTやブログなどで設計などをアウトプット PHP Sessionless Conferenceのコアスタッフになったばかり コミュニティ活動 @suzuki_mar プログラマー以外にプロレス団体のプロモーター的な 活動をボランティでしている 名古屋で3000人の会場を埋めることを考えている

Slide 4

Slide 4 text

登壇をする前に @suzuki_mar 今回がはじめての20分の登壇なので 不手際があるかもしれないですけど よろしくお願いします

Slide 5

Slide 5 text

話すこと @suzuki_mar

Slide 6

Slide 6 text

ユニットテストの目的を理解し、 プロダクションコードとテストコードの 依存関係を作らずに、 プログラミングの振る舞いに対して 各レイヤーの粒度にあった必要十分な テストを書いていく @suzuki_mar

Slide 7

Slide 7 text

話さないこと @suzuki_mar ユニットテストの書き方など ユニットテストを書いてあることを 前提としています

Slide 8

Slide 8 text

ユニットテストの本や資料はたくさん あるのでそちらをみてください @suzuki_mar t-wadaさんの TDDBC Sendaの動画 https://www.youtube.com/watch?v=r0o54M0J850

Slide 9

Slide 9 text

前提知識 ユースケースクラス @suzuki_mar

Slide 10

Slide 10 text

自分の設計ではユースケースクラスが肝になってい るので今回もそれを使って説明をします ユースケースクラスとはAPIのリクエスト、レスポン ス以外のプログラムが何をするのかを書くクラス 前提知識 ユーケースクラス @suzuki_mar

Slide 11

Slide 11 text

No content

Slide 12

Slide 12 text

ユースケースクラスを ADRパターンのACTIONクラス という認識でもOK 前提知識 ユーケースクラス @suzuki_mar

Slide 13

Slide 13 text

前提知識 ユーケースクラス @suzuki_mar Action リクエストを受け取り、ドメインの処理結果をレスポンダに渡す Domain 必要な処理を行い結果を返すビジネスロジックはここに含まれる Responder ドメインの処理結果を受け取り、必要な準備を行いレスポンスを返す

Slide 14

Slide 14 text

前提知識 ユーケースクラス @suzuki_mar 次のテキストをもとに 解説していきます

Slide 15

Slide 15 text

ユニットテストの目的を理解し、 プロダクションコードとテストコードの 依存関係を作らずに、 プログラムの振る舞いに対して 各レイヤーの粒度にあった必要十分な テストを書いていく

Slide 16

Slide 16 text

ユニットテストの目的 @suzuki_mar

Slide 17

Slide 17 text

よくある勘違い ユニットテストは プログラミングの品質を保証していて バグがないように テストをしているするもの ユニットテストの目的 @suzuki_mar

Slide 18

Slide 18 text

ユニットテストは、 開発者が想定している通りに プログラムが振る舞うことを テストを書きながら検証し、 保証するためのもの ユニットテストの目的 @suzuki_mar

Slide 19

Slide 19 text

開発者が仕様を誤解していたり 仕様自体に誤りがある場合は バグが発生する ユニットテストの目的 @suzuki_mar

Slide 20

Slide 20 text

ユニットテストではバグが発生する 可能性があるのになんで時間をかけて テストコードを書く必要があるのか ユニットテストの目的 @suzuki_mar

Slide 21

Slide 21 text

ユニットテストは開発者用のテストとして 想定外の場所でバグが発生していないことを保証したり このコードはこの通りに動くという前提で プログラミングをすることができる ユニットテストの目的 @suzuki_mar 開発者のための安心ガード

Slide 22

Slide 22 text

E2Eテストや受け入れテスト ユニットテストの目的 @suzuki_mar 品質保証テスト ユニットテスト t

Slide 23

Slide 23 text

E2Eテストや受け入れテスト ユニットテストの目的 @suzuki_mar 品質保証テスト ユニットテスト デベロッパーテスト t 想定通りに動作しているかを素早く確認し、開発をサポート

Slide 24

Slide 24 text

ユニットテストの目的 @suzuki_mar ユニットテストは開発者を サポートするためのテスト メンテナンスしやすいように テストを書いていくことが大切 t

Slide 25

Slide 25 text

ユニットテストの目的 @suzuki_mar ユニットテストがメンテナンス しづらいものだったら 開発者の足かせになってしまう 逆にt

Slide 26

Slide 26 text

ユニットテストの目的 @suzuki_mar メンテンナンスしやすいユニットテスト t

Slide 27

Slide 27 text

ユニットテストの目的 @suzuki_mar メンテンナンスしやすいユニットテスト t プロダクションコードが変更してもテストコードがこわれない

Slide 28

Slide 28 text

ユニットテストの目的 @suzuki_mar メンテンナンスしやすいユニットテスト t プロダクションコードが変更してもテストコードがこわれない 開発者が実装した振る舞いをおこなうことが 保証されている最小限のテストコード

Slide 29

Slide 29 text

プロダクションコード とテストコードで 依存関係をつくらない @suzuki_mar

Slide 30

Slide 30 text

プロダクションコードとテストコードの 依存関係を作らずに

Slide 31

Slide 31 text

プロダクションコートとテストコードの依存関係をつくらない @suzuki_mar 依存関係があるとは プロダクションコードの定義を テストコードでも定義を参照してしまうため プロダクションコードの変更時 テストコードも壊れる t

Slide 32

Slide 32 text

プロダクションコートとテストコードの依存関係をつくらない @suzuki_mar Userクラスに新しいユーザーを取得するメソッド User::fetchNewestが実装されているので 以下のテストコードを書いている t どういうことか $this->assertNotNull(User::fetchNewest());

Slide 33

Slide 33 text

プロダクションコートとテストコードの依存関係をつくらない @suzuki_mar 仕様変更が発生して新しいユーザー を取得するのではなくて 認証している新しいユーザー を取得するということになり次のメソッド名になった t User::fetchAuthenticatedNewest())

Slide 34

Slide 34 text

プロダクションコートとテストコードの依存関係をつくらない @suzuki_mar さきほどのテストコードを 次のように変更しないとけいない t $this->assertNotNull(User::fetchNewest()); $this->assertNotNull(User::fetchAuthenticatedNewest())

Slide 35

Slide 35 text

プロダクションコートとテストコードの依存関係をつくらない @suzuki_mar わかりやすい例がprivate Method に対してテストコードをかいてしまうため 依存関係が生まれる private MethodはPublic Method よりも変更をする頻度がおおいためテストコードを 変更する必要になってしまう t

Slide 36

Slide 36 text

プロダクションコートとテストコードの依存関係をつくらない @suzuki_mar 一つのメソッドだったら問題ないがこれが、 100個メソッドに対してテストを書いていたら 少しずつだけど塵が積もればとなり 負担になっていく t

Slide 37

Slide 37 text

プロダクションコートとテストコードの依存関係をつくらない @suzuki_mar プロダクションコードとテストコードが 依存しているとCIを実行すると 度々テストコードが Error: Call to undefined method など のようなエラーをおこしてしまう

Slide 38

Slide 38 text

プロダクションコートとテストコードの依存関係をつくらない @suzuki_mar その結果 虚無状態になり ユニットテストを書きたくなくなる

Slide 39

Slide 39 text

プロダクションコートとテストコードの依存関係をつくらない @suzuki_mar どのようなテストを書いていくか

Slide 40

Slide 40 text

プロダクションコートとテストコードの依存関係をつくらない @suzuki_mar プログラムの振る舞いに対 してテストを書いていく どのようなテストを書いていくか

Slide 41

Slide 41 text

プログラムの振る舞いに対して

Slide 42

Slide 42 text

プログラミングの振る舞い @suzuki_mar

Slide 43

Slide 43 text

プログラムの振る舞いは、定義された仕様に 従って動作することを指す 仕様の重要な部分は ユースケースとして記述される ユースケース記述は、各ユースケースの 詳細な流れや条件を説明している プログラミングの振る舞い @suzuki_mar

Slide 44

Slide 44 text

ユースケース記述は以下の項目が存在する アクター 誰が何をするか 前提条件 ユースケースを実行できるための条件 メインフロー  ユースケースとして何をしていくか 代替フロー メインフローが実行できない場合にどうするか 事後条件 ユースケースの実行後に達成される結果や状態 プログラミングの振る舞い @suzuki_mar

Slide 45

Slide 45 text

ユースケース記述の例 ECサイトの注文 プログラミングの振る舞い @suzuki_mar

Slide 46

Slide 46 text

アクタ9 主アクター:購入0 副アクター:システム、CS(カスタマーサポート) 前提条件 ・購入者のIDが正しいこと 事後条件 ・商品が購入されている プログラミングの振る舞い @suzuki_mar

Slide 47

Slide 47 text

メインフロー 支払い情報を入力するまA V1 システムは購入者がブラックリストに登録されていないことを確認すx %1 購入者がブラックリストに登録されている場合e 1 システムは購入者に購入できない旨を通知すx 1 ユースケースを終了すx 1 購入者が住所を入力すx 1 システムは外部のアドレスチェックAPIに入力された住所が正しいことを問い合わせx %1 住所が間違っている場! 1 システムは購入者に住所が間違っていることを通知すx 1 購入者に正しい住所の再入力を促す プログラミングの振る舞い @suzuki_mar

Slide 48

Slide 48 text

メインフロー 購入処理を終了するま` vb 購入者が支払い方法を選択する(クレジットカード、PayPay、モバイル決済‚ "b システムは購入者の支払い能力を確認す€ 'b 購入者の支払情報が間違っている場 &b システムは購入者に支払い情報が間違っていることを通知す€ &&b 購入者に正しい支払い情報の再入力を促p &&&b 支払い能力を確認できない場合はユースケースを終了す€ Àb 購入者が商品を購入する プログラミングの振る舞い @suzuki_mar

Slide 49

Slide 49 text

サンプルコード プログラミングの振る舞い @suzuki_mar

Slide 50

Slide 50 text

No content

Slide 51

Slide 51 text

正常処理 購入に成功したことを確認している

Slide 52

Slide 52 text

ユースケースの実行Ⅱ 失敗したことのテスト ブラックリストに登録されている場合はカスタム例外ク ラスが発生している振る舞いをしているはず プロダクションコードとテストコードが依存している例 メッセージを変更するたびにテストコードを変更する必要がある 他の失敗したことのテストは同様に書いていく

Slide 53

Slide 53 text

先ほどのようにテストコードを書いていけば、 実装者はユースケースがプログラムが想定している とおりに振る舞うと自信を持って言える プログラミングの振る舞い @suzuki_mar

Slide 54

Slide 54 text

BlackList::checkなどは 本当に自信をもっていると 動いていると言えるか プログラミングの振る舞い @suzuki_mar

Slide 55

Slide 55 text

ユーザーをブラックリストに入っていると 扱う条件は複雑 プログラミングの振る舞い @suzuki_mar 繰り返しの支払い失敗 不正な返金要求やチャージバックの頻発 虚偽の「商品未着」申告 不自然な頻度での返品や交換 複数アカウントの不正利用

Slide 56

Slide 56 text

ただ、これをユースケースのテストを書くと テストが複雑になってしまう そのため、BlackListのユニットテスト を書いたほうがいい プログラミングの振る舞い @suzuki_mar

Slide 57

Slide 57 text

Addressの確認はただ、 外部APIにアクセスするだけのシンプルな場合 AddressCheckerのテストを書かないでも ユースケースのテストだけで動いていると 自信を持っても良い プログラミングの振る舞い @suzuki_mar

Slide 58

Slide 58 text

実装者が複雑だと思うものに対してだけ クラスに対してのテストコードを書いていく プログラミングの振る舞い @suzuki_mar

Slide 59

Slide 59 text

各レイヤーの粒度にあった 必要十分な テストを書いていく @suzuki_mar

Slide 60

Slide 60 text

各レイヤーの粒度にあった必要十分な テストを書いていく

Slide 61

Slide 61 text

ユースケースクラスがある場合の各レイヤー Featureテスト  UseCaseテスト 個別のクラスのテスト 各レイヤーの粒度にあった必要十分なテストを書いていく @suzuki_mar

Slide 62

Slide 62 text

ユースケースの実行が正しく振る舞っていると過程して APIへのリクエストパラメーターのバリデーション等 が正しく実行されてること ユースケースが実行されている レスポンスで返すが正しいレスポンスコードなどが問題ないこと Featureテスト APIが正しく振る舞っていることの確認  各レイヤーの粒度にあった必要十分なテストを書いていく @suzuki_mar

Slide 63

Slide 63 text

ステータスコードが正しいことのテスト

Slide 64

Slide 64 text

UseCaseが正しく実行されていることのテスト UseCaseが正しく振る舞っていると ordersにデータが作成されるという前提に立てる UseCaseが正しく振る舞っていることはUseCaseのテストで保証されている これだとUseCaseクラスのクラス名やメソッド名が変わっても テストコードを書き直す必要がない

Slide 65

Slide 65 text

ユースケース記述どおりに 振る舞っているかをテストする さきほど説明した以下のようなコードを書く UseCaseテスト 

Slide 66

Slide 66 text

実装者が自信を持ててコードを書いていないものに 対してだけテストを書くメソッド名などは プロダクションコードと依存してもよい クラスに対するテスト  一般的に簡単だと思えることでも実装者が難しい と思う場合はクラスに対するテストコードを書く

Slide 67

Slide 67 text

クラスに対するテスト 

Slide 68

Slide 68 text

BlackList::Checkは仕様が複雑だけどクラスに対してテスト コードをかいているため実装者の実装通りに クラスが振る舞うという前提にたてる そのため、BlackListに関することの ユースケースのテストは簡単なものでいい

Slide 69

Slide 69 text

図解 Featureテスト (送信されたパラメーターが正しいかどなど) UseCaseテスト (ユースケース記述どおりにテストがじっこうさること) Featureテスト (UseCaseが実行されているか、ステータスコードが正しいか) 複雑な個別のクラスの テスト

Slide 70

Slide 70 text

このようにテストコード を作成すると必要十分な テストを書くことができる 各レイヤーの粒度にあった必要十分なテストを書いていく @suzuki_mar

Slide 71

Slide 71 text

テストコードが少ないほど ユニットテストのメンテナンス するものが少なくなる 結果、変更に強いユニットテストになる 各レイヤーの粒度にあった必要十分なテストを書いていく @suzuki_mar

Slide 72

Slide 72 text

まとめ @suzuki_mar

Slide 73

Slide 73 text

ユニットテストの目的 ユニットテストは開発者を サポートするためのテスト メンテナンスしやすいように テストを書いていくことが大切 各レイヤーの粒度にあった必要十分なテストを書いていく @suzuki_mar

Slide 74

Slide 74 text

プロダクションコードと テストコードの依存関係 各レイヤーの粒度にあった必要十分なテストを書いていく 依存しているとCIを実行すると 度々テストコードが Error: Call to undefined method など のようなエラーをおこしてしまう @suzuki_mar

Slide 75

Slide 75 text

プログラムの振る舞い 各レイヤーの粒度にあった必要十分なテストを書いていく 定義された仕様に 従って動作することを指す 仕様の重要な部分は ユースケースとして記述される ユースケース記述は、各ユースケースの 詳細な流れや条件を説明している @suzuki_mar

Slide 76

Slide 76 text

各レイヤーの粒度にあった 各レイヤーの粒度にあった必要十分なテストを書いていく Featureテスト (送信されたパラメーターが正しいかどなど) UseCaseテスト (ユースケース記述どおりに テストが実行するか) Featureテスト (UseCaseが実行されているか、 ステータスコードが正しいか) 複雑な個別 のクラスの テスト

Slide 77

Slide 77 text

ユニットテストの目的を理解し、 プロダクションコードとテストコードの 依存関係を作らずに プログラムの振る舞いに対して 各レイヤーの粒度にあった必要十分な テストを書いていく @suzuki_mar

Slide 78

Slide 78 text

新しい仕様が加わったら どうテストを書くか @suzuki_mar

Slide 79

Slide 79 text

みなさんも一緒に 考えてください @suzuki_mar 新しい仕様が加わったらどうテストを書くか

Slide 80

Slide 80 text

新しい仕様が加わったらどうテストを書くか @suzuki_mar 在庫チェック RDBで在庫を管理しています 在庫が存在しない場合は エラーステータスコードを返す

Slide 81

Slide 81 text

新しい仕様が加わったらどうテストを書くか @suzuki_mar 次のテストを書く

Slide 82

Slide 82 text

新しい仕様が加わったらどうテストを書くか @suzuki_mar 在庫がない場合にユースケーステストで エラーが発生することをテストする 次のテストを書く

Slide 83

Slide 83 text

在庫チェックはDBに データがあればいいので ユースケースだけのテストで十分 新しい仕様が加わったらどうテストを書くか @suzuki_mar

Slide 84

Slide 84 text

新しい仕様が加わったらどうテストを書くか @suzuki_mar 在庫がないエラーがユースケースで発生した場合に APIのレスポンスとしてエラーステータスコード を返していることをテストする 次のテストを書く 在庫がない場合にユースケーステストで エラーが発生することをテストする

Slide 85

Slide 85 text

在庫チェックでエラーが発生した場合は 新しくエラーステータスを返す必要 があるのでFeatureTestにそれを追加する 新しい仕様が加わったらどうテストを書くか @suzuki_mar

Slide 86

Slide 86 text

新しい仕様が加わったらどうテストを書くか @suzuki_mar ポイント付与 ルール 通常のポイント付与 会員のステータスに応じた追加ポイント 商品の種類によるポイント付与率の変動 特定期間中の特別なポイント付与ルール 顧客の最初の購入に対する特別ボーナス 高額購入に対する追加ポイント

Slide 87

Slide 87 text

ポイント付与ルールは複雑なので 何ポイントを 付与するクラスを作成して そのクラスのためのテストを書く 新しい仕様が加わったらどうテストを書くか @suzuki_mar

Slide 88

Slide 88 text

No content

Slide 89

Slide 89 text

ユースケースのテストにポイントが 付与されていることのテストをする クラスに対するテストを書いているため 何かしらのポイントが付与されていることを確認するだけでいい 新しい仕様が加わったらどうテストを書くか @suzuki_mar

Slide 90

Slide 90 text

新しい仕様が加わったらどうテストを書くか @suzuki_mar

Slide 91

Slide 91 text

ポイント付与されても APIの振る舞いには 変化がないので FeatureTestにはテストを追加しない 新しい仕様が加わったらどうテストを書くか @suzuki_mar

Slide 92

Slide 92 text

参考文献 @suzuki_mar

Slide 93

Slide 93 text

参考文献 @suzuki_mar 単体テストや統合テストの基本から設計の重 要性まで幅広く解説し、 実践的なアプローチを提供しています。 Mocking戦略やTDDの設計行為としての役割 にも触れており、 テストコードの問題点にも洞察が得られま す。 引用 https://www.amazon.co.jp/product-reviews/4839981728/ref=acr_dpx_hist_5? ie=UTF8&filterByStar=five_star&reviewerType=all_reviews#reviews-filter-bar

Slide 94

Slide 94 text

参考文献 @suzuki_mar ユースケースに基づく設計手法を丁寧に解説 し、設計から実装までの具体的な流れを学べ ます。複雑な要件整理やモデリングの実践例 も豊富に含まれ、理論だけでなく実践的なス キルを身につけたい開発者にとって非常に役 立つ一冊です。 引用 https://www.amazon.co.jp/product-reviews/4839981728/ref=acr_dpx_hist_5? ie=UTF8&filterByStar=five_star&reviewerType=all_reviews#reviews-filter-bar

Slide 95

Slide 95 text

終わり