Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
テスト駆動開発試してみた発表
Search
tarohida
June 10, 2020
Programming
0
170
テスト駆動開発試してみた発表
自作のTwitterbotアプリのリファクタリングついでに、TDDを実際に取り入れてテストコードを書いてみました。
その際得られた知見を共有します。
tarohida
June 10, 2020
Tweet
Share
More Decks by tarohida
See All by tarohida
リモートワークをきっかけに、見積もりと進捗について考えてみた
tarohida
0
35
事故について考えてみようの会
tarohida
0
130
Other Decks in Programming
See All in Programming
CloudflareStack でRAGに入門
asahiiwm
0
140
Jakarta EE meets AI
ivargrimstad
0
370
命名をリントする
chiroruxx
1
520
Итераторы в Go 1.23: зачем они нужны, как использовать, и насколько они быстрые?
lamodatech
0
1.2k
nekko cloudにおけるProxmox VE利用事例
irumaru
3
510
開発者とQAの越境で自動テストが増える開発プロセスを実現する
92thunder
1
220
非ブラウザランタイムとWeb標準 / Non-Browser Runtimes and Web Standards
petamoriken
0
400
責務を分離するための例外設計 - PHPカンファレンス 2024
kajitack
9
2.2k
AppRouterを用いた大規模サービス開発におけるディレクトリ構成の変遷と問題点
eiganken
1
380
Mermaid x AST x 生成AI = コードとドキュメントの完全同期への道
shibuyamizuho
1
490
PHPカンファレンス 2024|共創を加速するための若手の技術挑戦
weddingpark
0
110
php-conference-japan-2024
tasuku43
0
390
Featured
See All Featured
Building Your Own Lightsaber
phodgson
104
6.1k
ピンチをチャンスに:未来をつくるプロダクトロードマップ #pmconf2020
aki_iinuma
111
50k
Building Flexible Design Systems
yeseniaperezcruz
328
38k
Put a Button on it: Removing Barriers to Going Fast.
kastner
59
3.6k
Music & Morning Musume
bryan
46
6.3k
Build your cross-platform service in a week with App Engine
jlugia
229
18k
RailsConf 2023
tenderlove
29
960
Visualization
eitanlees
146
15k
The Psychology of Web Performance [Beyond Tellerrand 2023]
tammyeverts
45
2.3k
The Language of Interfaces
destraynor
155
24k
Fantastic passwords and where to find them - at NoRuKo
philnash
50
2.9k
Building Better People: How to give real-time feedback that sticks.
wjessup
366
19k
Transcript
テスト駆動開発試してみた t-hida
TDDについての説明 以下の資料が分かりやすかったので、概要については以下の資料に基づいて説明しま す。 https://gihyo.jp/dev/serial/01/tdd/0001
テスト駆動開発とは? 単にテストを書けばテスト駆動開発ではない
テスト駆動開発とは? 「テスト駆動開発」p.298 「訳者解説:テスト駆動開発の現在」より > テストコードを書くプログラマが増えるに従って、いくつかの問題が出てきてしまいまし た。具体的には、テストコードを書くことが形骸化、あるいは自己目的化してしまい、テス トの価値や目的があやふやになってしまったり、脆いテストコードや遅いテスト、不可解 な失敗の仕方をするテストが、プロジェクトのメンテナンスコストを増大させたりしていま した。 またTDDについても、テストを先に書くことがTDDであるという誤解が生じていました。
テスト駆動開発とは? 小さなフィードバックループを回していくことによって、開発を効率的に進めていくのがテ スト駆動開発
テスト駆動開発とは? どのようにフィードバックループを回すのか? -> テスト駆動開発の3ステップ
テスト駆動開発とは? テスト駆動開発の3ステップ • ステップ1:これから書く機能に対するテストを1つ書き,テストが失敗することを確認 する(レッド) • ステップ2:ステップ1のテストを通す最低限のコードを実装する(グリーン) • ステップ3:リファクタリングを行う(リファクタリング) https://gihyo.jp/dev/serial/01/tdd/0002より
テスト駆動開発とは? この3ステップについては、後ほど実際にコーディングしてみます。
テストとは? 一般的な分類 • 「ユニットテスト」「 単体テスト」 • 「 機能テスト」「 結合テスト 」
• 「システムテスト」 https://gihyo.jp/dev/serial/01/tdd/0004
テストとは? 説明のため3種類に分類してみる (t-wada独自) - Developer Testing:開発者用 - Customer Testing:進捗管理用 -
QA Testing:品質管理用 TDDで用いるのは、開発者が開発者のために書くテスト、フィードバックを得るためのテ スト、Developer Testingとここでは位置づける https://gihyo.jp/dev/serial/01/tdd/0003
テストとは? 説明のため3種類に分類してみる (t-wada独自) - Developer Testing:開発者用 ←テスト駆動開発の対象範囲 - Customer Testing:進捗管理用
- QA Testing:品質管理用
Developer Testing > 先述したように,テスト駆動開発におけるテストというのは,品質のためのテストでは ありません。テスト駆動開発でのテストの位置づけは,開発を先に進めて行くための「は ずみ車 」 ,開発を前に進める「原動力」です。 > 自分が書いたコードは自分でテストします。そして,テストを書くことで,フィードバック
を得ることができます。「 次はこうしなきゃいけないはずだ」「 こういうコードのほうがいい んじゃないか」――テストがもたらしてくれる情報をもとにコードをより良くしていこうという のが,テスト駆動開発におけるテスト,つまりDeveloper Testingの役割です。 https://gihyo.jp/dev/serial/01/tdd/0003
実際にテスト駆動開発の3ステップをやってみます https://github.com/taro-hida/testcace_practice
テスト駆動開発をしばらく取り入れてみて メリット - メソッド内部のリファクタリングがしやすくなった テストケースに通ったあとは、引数と返り値を変更しないということさえ守れば心置 きなくリファクタリングができる
テスト駆動開発をしばらく取り入れてみて メリット - var_dump() でのデバッグがしやすくなった 実行単位がテストケース単位 (=メソッド単位)となるので、エラーが発生した際に var_dumpを仕込むのが安易 (実行もCLIなので、エラー出力による確認もしやすい) テストコードから更に問題を分割して、マイクロプログラムを作成して挙動を確認す
ることも容易
テスト駆動開発をしばらく取り入れてみて メリット - エラーが出た際、自分のどの作業によってバグが発生したのかがわかりやすくなっ た - これは、すごく小さいループを回した結果得られた効果 > 失敗するテストを書いてから、最初に行う実装はどのようなものだろうか ―
ベタ書きの値を返そう。それでテストが通るようになったら、ベタ書きの値をだんだん本物の式や変 数に置き換えていく。 >「テスト駆動開発」Kent Beck著 和田卓人訳 P.217 「グリーンバーのパターン」
テスト駆動開発をしばらく取り入れてみて メリット > エラーが出た際、自分のどの作業によってバグが発生したのかがわかりやすくなった 効果を実感したテストケース /** * @depends testConstruct */
public function testCreateMessage(OnanieCounter $onanieCounter) { $user_id = '12121212121212121'; $screen_name = 'taro'; $user_name = 'Taro'; $this->assertSame("@taro Taroさんの一日あたりのツイート数は0.25回です\n, tweet_counter->createMessage($user_id, $screen_name, $user_name)); }
テスト駆動開発をしばらく取り入れてみて メリット > エラーが出た際、自分のどの作業によってバグが発生したのかがわかりやすくなった テストケースに対応するメソッド public function createMessage(string $user_id, string
$screen_name, string $user_name): string { $tweet_count = $this->getTweetCount($user_id); return "@{$screen_name} {$user_name}さんの一日あたりのツイート数は{$tweet_count}回です\n"; }
テスト駆動開発をしばらく取り入れてみて メリット > エラーが出た際、自分のどの作業によってバグが発生したのかがわかりやすくなった 最初から全部書いてしまうと、どこがおかしいのかわからない - 自分の書いたテストケースがそもそもおかしいのか - クラス名、メソッド名、ネームスペース等をタイポして読み込めていないのか -
returnする値が間違っているのか - 変数名が間違っているのか - 外部メソッドの呼び出し方が間違っているのか - 外部メソッドが返した値がおかしいのか
テスト駆動開発をしばらく取り入れてみて メリット > エラーが出た際、自分のどの作業によってバグが発生したのかがわかりやすくなった 最初から全部書いてしまうと、どこがおかしいのかわからない -> メソッドの返り値をベタ書きから始めると、どの変更によってエラーが発生したのか、 わかる!
テスト駆動開発をしばらく取り入れてみて メリット > エラーが出た際、自分のどの作業によってバグが発生したのかがわかりやすくなった テストケースに対応するメソッド public function createMessage(string $user_id, string
$screen_name, string $user_name): string { $tweet_count = $this->getTweetCount($user_id); return "@{$screen_name} {$user_name}さんの一日あたりのツイート数は{$tweet_count}回です\n"; }
テスト駆動開発をしばらく取り入れてみて メリット > エラーが出た際、自分のどの作業によってバグが発生したのかがわかりやすくなった まずベタ書きする -> この状態でエラーが出た場合は、クラス名やテストケースの書き方、 テストケースでの初期化処理に問題がある。 public function
createMessage(string $user_id, string $screen_name, string $user_name): string { $tweet_count = $this->getTweetCount($user_id); return "@taro Taroさんの一日あたりのツイート数は0.25回です\n,; }
テスト駆動開発をしばらく取り入れてみて メリット > エラーが出た際、自分のどの作業によってバグが発生したのかがわかりやすくなった 変数を移動する -> この状態でエラーが出たときは、変数の受け渡しに問題がある。 public function createMessage(string
$user_id, string $screen_name, string $user_name): string { $tweet_count = $this->getTweetCount($user_id); return "@{$screen_name} {$user_name}さんの一日あたりのツイート数は0.25回です\n"; }
テスト駆動開発をしばらく取り入れてみて メリット > エラーが出た際、自分のどの作業によってバグが発生したのかがわかりやすくなった 内部的に呼び出しているメソッドの返り値を移動する -> この状態でエラーが出たときは、メソッドおよび、メソッドへの値の引き渡し方に問題がある public function createMessage(string
$user_id, string $screen_name, string $user_name): string { $tweet_count = $this->getTweetCount($user_id); return "@{$screen_name} {$user_name}さんの一日あたりのツイート数は{$tweet_count}回です\n"; }
テスト駆動開発をしばらく取り入れてみて メリット > エラーが出た際、自分のどの作業によってバグが発生したのかがわかりやすくなった このように、(今回の例は細かすぎるけれども)問題を細かく分割して考えられるのが、 テスト駆動開発の手順を踏むことによるメリット public function createMessage(string $user_id,
string $screen_name, string $user_name): string { $tweet_count = $this->getTweetCount($user_id); return "@{$screen_name} {$user_name}さんの一日あたりのツイート数は{$tweet_count}回です\n"; }
テスト駆動開発をしばらく取り入れてみて メリット > エラーが出た際、自分のどの作業によってバグが発生したのかがわかりやすくなった 副次的なメリット - コーディングにおいて、一歩一歩確実に進んでいく実感が得られた 今回であれば... 「引数を移動して、テストがパスした」 ->
残りの作業は、内部的に呼び出したメソッドの返り値を、テスト対象のメソッドの 返り値に受け渡すだけ
テスト駆動開発をしばらく取り入れてみて 難しいと感じた点 一言で言えば、「設計が難しい!」と感じました。
テスト駆動開発をしばらく取り入れてみて 難しいと感じた点 > - リファクタリングをする過程において、他のメソッドの呼び出し方や、取る引数を変 更したくなる 割と頻繁に既存のテストを何度も何度も書き換えることになる いったり来たりして結構めんどう
テスト駆動開発をしばらく取り入れてみて 難しいと感じた点 > - ひとつのメソッドのためのひとつのテストを書いているうちに、そのメソッドのために 必要なもう一つのメソッドを書く必要があることに気づく 例:リプライ元に、”あなたの今日のツイート数は◦◦です”と返答する -> よくよく考えると、その文章を作成するためのメソッドが必要だな ->
並行して2つのメソッドを考え始めるので、脳みそがしんどくなる
テスト駆動開発をしばらく取り入れてみて 難しいと感じた点 > - コードの構造を考えるにおいて、テストを書いていなかった頃とは異なるアプローチ が必要になった 今まで:挙動と実装、内部処理から組み立てていく テスト駆動:満たすべき要件とテストから組み立てていく 今までと同じ手法で取り組むと、アホほどテストケースを書き直す必要が発生する。 ある程度考えてから書き始めないといけない。
テスト駆動開発をしばらく取り入れてみて 気づいた点 > - インスタンスに状態をもたせると、テストを実施しにくくなる - 同じことを言っていた記事 > このとき、悪性の Singleton
が入力にあると、テストコードから Singleton を意図した状態へもっていく必要がでてき ます。すると、テストコードの実行前に Singleton の状態に応じた分岐を書く必要がでてきます。これはテストの本質と は関係のないコードであるため、テストコードの記述量を余計に増やす上に、見通しをとても悪くさせます https://qiita.com/Kuniwak/items/4314451227f4d5eaa6b8#%E8%87%AA%E5%8B%95%E3%83%86%E3%82%B9%E3 %83%88%E3%81%8C%E3%81%AA%E3%81%84%E3%81%A8%E4%BD%95%E3%81%8C%E8%B5%B7%E3%81% 93%E3%82%8B%E3%81%AE%E3%81%8B - また、インスタンスを生成するまでの事前準備の処理がすごく長くなる
テスト駆動開発をしばらく取り入れてみて 気づいた点 > - テストケースによるDB操作は、そこまで時間がかからなかった - ただし、DB操作をテストに盛り込むのはアンチパターン - TDDにおいては、DBから値を直接取り出すのは NG、テストコードから直接サンプルの値を引き渡
すのが望ましい - 速度だけ考えれば、 DBから引っ張ってきた値で代用することはできそう 今回:sqlダンプで本番環境から吐き出した sqlを手元dockerのpostgres環境に流し込んで、本番環 境で利用していたレコードをテストでも利用 https://openmoji.org/library/#search=thinking&emoji=1F914
テスト駆動開発をしばらく取り入れてみて ミスったこと - @dependsを多用した
テスト駆動開発をしばらく取り入れてみて ミスったこと > @dependsを多用した PHPUnitのドキュメントで上から順に学習を進めると、testConstruct()を作成し、そ の返り値を@dependsで次のテストに引き渡すテストケースが完成する https://phpunit.readthedocs.io/ja/latest/writing-tests-for-phpunit.html
テスト駆動開発をしばらく取り入れてみて ミスったこと > @dependsを多用した @dependsで値を引き渡すと不便な点 - テストを単体で実行しようとすると、testConstructが実行されていないのでテ ストが失敗する - 頻繁に@dependsのアノテーションを書き忘れる。タイポする。
引数としての指定を忘れる - @dataProviderの引数と順番を間違える (@dependsの引数は後に来る)
テスト駆動開発をしばらく取り入れてみて ミスったこと > @dependsを多用した setUp()を利用して、TestCaseのプライベート変数としてインスタンスを格納したほう がよい - テストケース毎に初期化されるようになるので、その点注意は必要
テスト駆動開発をしばらく取り入れてみて ミスったこと > 引数としての多次元配列の乱用 - PDOStatement::fetchAll()、TwitterOAuth::get()などの多次元配列をそのまま渡 すコードを書いていた テスト用の値を渡してくれるdataProviderメソッドが非常に長くなったり、簡単に書け る分量でなくなったりする 引数の受け渡しは極力、必要最低限にする方がテストはしやすそう。
-> ただ、全部渡しておいたほうが内部的な変更はしやすそうだなとも感じる
テスト駆動開発をしばらく取り入れてみて やっといてよかったこと - PHPUnitの使い方に慣れる - 写経する。 - 簡単なテストケースを繰り返し作成して、テストケースを作成する作業に慣れる。 PHPUnitは非常に使いやすいツールですが、これにまずは慣れないと、ツールの 使い方に脳のリソースが持ってかれてテスト駆動開発どころではなくなるという状況
に陥りました。
まとめ - テスト駆動開発を行うことによって実感したメリット フィードバックループを回すことによって、一歩一歩確実に開発をすすめることができる 自分の書いたコードが、本当に挙動として自分の思っているとおりに書けているか、セルフ チェックができる - 難しい点 設計
参考 - [動画で解説]和田卓人の“テスト駆動開発”講座 https://gihyo.jp/dev/serial/01/tdd/0001 - 新訳版『テスト駆動開発』 https://t-wada.hatenablog.jp/entry/tddbook