Upgrade to Pro — share decks privately, control downloads, hide ads and more …

リーダブルテストコード 〜メンテナンスしやすい テストコードを作成する方法を考える〜 #Dev...

nihonbuson
February 13, 2025

リーダブルテストコード 〜メンテナンスしやすい テストコードを作成する方法を考える〜 #DevSumi #DevSumiB / Readable test code

nihonbuson

February 13, 2025
Tweet

More Decks by nihonbuson

Other Decks in Technology

Transcript

  1. // 店舗スタッフとしてログインする I.amOnPage("/"); I.click("ログインする"); I.fillField("ユーザー名", "admin"); I.fillField("パスワード", "admin"); I.click("ログイン") //

    この後の処理は、暗黙に「店舗スタッフとしてログイン後」を期待されている I.click("スタッフ用マイページ") コンテキストが明示されない例 (1) ログイン状態
  2. // ここから商品一覧ページ I.amOnPage("/items"); const itemContainer = locate("tr").withText("トマト") I.click("商品を編集", itemContainer); //

    ここから商品編集ページ I.fillField("商品名", "プチトマト"); I.click("変更"); // ここまで商品編集ページ // ここから商品一覧ページ 同じ要領でページ遷移も
  3. I.shouldBeOnItemListPage(I => { const itemContainer = locate('tr').withText("トマト") I.click("商品を編集", itemContainer(itemName)); I.shouldBeOnItemDetailPage(I

    => { I.fillField("商品名", "プチトマト"); I.click("変更"); }) }) 「あるページにいる」というコンテキストを表現する
  4. // テスト用の商品を追加する I.click("商品を追加する"); const itemName = `牛ハラミ弁当-テス ト-${utils.now.format("YYYYMMDDHHmmss")}`; I.fillField("商品名", itemName);

    I.fillField("商品説明", "テスト用の商品です"); I.fillField("価格", "500"); I.click("追加"); データの準備も
  5. 自己紹介 • 風間裕也(ブロッコリー) • 株式会社10X 品質管理チーム • 副業:B-Testing(個人事業主)として ◦ 株式会社MonotaRO

    (テストコンサルタント) ◦ グロース・アーキテクチャ&チームス株式会社 他数社でお手伝い • 社外活動 ◦ JaSST Review(ソフトウェアレビューシンポジウム) 実行委員長 ◦ WACATE(テストの合宿型ワークショップ形式勉強会) 実行委員長 ◦ SReEE(ソフトウェアレビューをエンジニアリング っぽく捉える会)リーダー SNS上の アイコン
  6. 理解容易性や説明容易性も重要なのでは? • 理解容易性(Understandability) ◦ 対象の理解しやすい度合い ◦ 可読性(Readability)に影響を受ける • 説明容易性(説明可能性、Explainability) ◦

    「今やっていることは何か」を説明できる度合い ◦ 読み手のコンテキストにも左右される ◦ 特にAI分野では「eXplainable AI(説明可能なAI)」 として注目されている ◦ 可読性(Readability)に影響を受ける オリジナル の用語
  7. 在庫処理のテストケース @Test public void 手動設定ありのパターン{ ProductStock productStock = new ProductStock(“商品A”);

    productStock.setBaseStock(10); productStock.setReceivedStock(6); productStock.setNsCoefficient(0.5); productStock.setManualOverride(7); assertThat( productStock.calc() , is(7)); }
  8. 会話からテストの意図を理解する @Test public void 手動設定ありのパターン{ ProductStock productStock = new ProductStock(“商品A”);

    productStock.setBaseStock(10); productStock.setReceivedStock(6); productStock.setNsCoefficient(0.5); productStock.setManualOverride(7); assertThat( productStock.calc(), is(7) ); } これって
 どうして最終的な
 期待値が7なんですか?
 えっとそれは…
 QA 開発者
  9. 会話からテストの意図を理解する @Test public void 手動設定ありのパターン{ ProductStock productStock = new ProductStock(“商品A”);

    productStock.setBaseStock(10); productStock.setReceivedStock(6); productStock.setNsCoefficient(0.5); productStock.setManualOverride(7); assertThat( productStock.calc() , is(7)); } まず、
 商品Aのクラスを
 生成して…
 開発者
  10. 会話からテストの意図を理解する @Test public void 手動設定ありのパターン{ ProductStock productStock = new ProductStock(“商品A”);

    productStock.setBaseStock(10); productStock.setReceivedStock(6); productStock.setNsCoefficient(0.5); productStock.setManualOverride(7); assertThat( productStock.calc() , is(7)); } 元々の在庫数の10個を
 setBaseStockで
 設定して…
 開発者
  11. 会話からテストの意図を理解する @Test public void 手動設定ありのパターン{ ProductStock productStock = new ProductStock(“商品A”);

    productStock.setBaseStock(10); productStock.setReceivedStock(6); productStock.setNsCoefficient(0.5); productStock.setManualOverride(7); assertThat( productStock.calc() , is(7)); } 新たに入庫された6個を
 setReceivedStockで
 設定するので、
 合計の在庫は
 10+6=16個で…
 開発者
  12. 会話からテストの意図を理解する @Test public void 手動設定ありのパターン{ ProductStock productStock = new ProductStock(“商品A”);

    productStock.setBaseStock(10); productStock.setReceivedStock(6); productStock.setNsCoefficient(0.5); productStock.setManualOverride(7); assertThat( productStock.calc() , is(7)); } 得られた在庫数は
 実店舗とネットスーパー上
 での共用となるので、
 全体の在庫の50%を
 ネットスーパー用に
 確保するために、
 setNsCoefficientで
 設定するので、
 16×0.5=8個となって…
 開発者
  13. 会話からテストの意図を理解する @Test public void 手動設定ありのパターン{ ProductStock productStock = new ProductStock(“商品A”);

    productStock.setBaseStock(10); productStock.setReceivedStock(6); productStock.setNsCoefficient(0.5); productStock.setManualOverride(7); assertThat( productStock.calc() , is(7)); } 最後に手動で設定する値
 “setManualOverride”が
 あれば、何があっても
 ネットスーパーの
 在庫数として
 上書きされるので、
 今回の在庫は7個に
 なります。
 開発者
  14. 会話からテストの意図を理解する @Test public void 手動設定ありのパターン{ ProductStock productStock = new ProductStock(“商品A”);

    productStock.setBaseStock(10); productStock.setReceivedStock(6); productStock.setNsCoefficient(0.5); productStock.setManualOverride(7); assertThat( productStock.calc() , is(7)); } なるほどー!
 ありがとうございます。
 QA
  15. 会話からテストの意図を理解する @Test public void 手動設定ありのパターン{ ProductStock productStock = new ProductStock(“商品A”);

    productStock.setBaseStock(10); productStock.setReceivedStock(6); productStock.setNsCoefficient(0.5); productStock.setManualOverride(7); assertThat( productStock.calc() , is(7)); } QA 説明を聞いていると、
 「setManualOverrideがあ れば、
 何があっても 
 ネットスーパーの 
 在庫数として 
 上書きされるので」 
 という部分が
 今回のテストの意図の
 ように聞こえました
 ああ、確かにそうかも
 開発者
  16. テストの意図を記載して説明容易性を上げる @Test public void 手動設定ありのパターン{ ProductStock productStock = new ProductStock(“商品A”);

    productStock.setBaseStock(10); productStock.setReceivedStock(6); productStock.setNsCoefficient(0.5); productStock.setManualOverride(7); assertThat( productStock.calc() , is(7)); } なので、それを
 そのまま
 テストメソッド名に
 しちゃいましょう!
 QA
  17. テストの意図を記載して説明容易性を上げる @Test public void 手動設定の値があれば必ず 最終的な在庫数として上書き設定する { ProductStock productStock =

    new ProductStock(“商品A”); productStock.setBaseStock(10); productStock.setReceivedStock(6); productStock.setNsCoefficient(0.5); productStock.setManualOverride(7); assertThat( productStock.calc() , is(7)); } なので、それを
 そのまま
 テストメソッド名に
 しちゃいましょう!
 QA
  18. 利点1.特別な設定値がどれなのか分かる @Test public void 手動設定ありのパターン{ ProductStock productStock = new ProductStock(“商品A”);

    productStock.setBaseStock(10); productStock.setReceivedStock(6); productStock.setNsCoefficient(0.5); productStock.setManualOverride(7); assertThat( productStock.calc() , is(7)); } テストの意図が 書かれていないと、 テストメソッド内 にある、 “10”, “6”, “0.5”, “7” がそれぞれ 特別な意味を 持っている ように見えてしまう
  19. 利点1.特別な設定値がどれなのか分かる テストの意図が 書かれていると、 重要な箇所が分かる その他の値は、 テストするために 設定した 任意の値だと分かる @Test public

    void 手動設定の値があれば必ず 最終的な在庫数として 上書き設定 する{ ProductStock productStock = new ProductStock(“商品A”); productStock.setBaseStock(10); productStock.setReceivedStock(6); productStock.setNsCoefficient(0.5); productStock.setManualOverride(7); assertThat( productStock.calc() , is(7)); }
  20. 利点2.仕様変更時に対応しやすい 例えば、 「手動設定 setManualOverride は考えなくても 良いことになった」 という仕様変更が 出てきたとき… @Test public

    void 手動設定ありのパターン{ ProductStock productStock = new ProductStock(“商品A”); productStock.setBaseStock(10); productStock.setReceivedStock(6); productStock.setNsCoefficient(0.5); productStock.setManualOverride(7); assertThat( productStock.calc() , is(7)); }
  21. 利点2.仕様変更時に対応しやすい プロダクトコードで setManualOverride() の処理を 削除したとする 連動して、 このテストコードは 通らなくなる @Test public

    void 手動設定ありのパターン{ ProductStock productStock = new ProductStock(“商品A”); productStock.setBaseStock(10); productStock.setReceivedStock(6); productStock.setNsCoefficient(0.5); productStock.setManualOverride(7); assertThat( productStock.calc() , is(7)); }
  22. 利点2.仕様変更時に対応しやすい テストの意図が 書かれていないと、 このテストが どんなことを したかったのか 改めて読み込む 必要がある @Test public

    void 手動設定ありのパターン{ ProductStock productStock = new ProductStock(“商品A”); productStock.setBaseStock(10); productStock.setReceivedStock(6); productStock.setNsCoefficient(0.5); productStock.setManualOverride(7); assertThat( productStock.calc() , is(7)); }
  23. 利点2.仕様変更時に対応しやすい テストの意図が 書かれていることで、 今回はテストケース そのものを削除する 対応で良いと 即時に判断できる @Test public void

    手動設定の値があれば必ず 最終的な在庫数として 上書き設定 する{ ProductStock productStock = new ProductStock(“商品A”); productStock.setBaseStock(10); productStock.setReceivedStock(6); productStock.setNsCoefficient(0.5); productStock.setManualOverride(7); assertThat( productStock.calc() , is(7)); }