テスト駆動開発
会社の人の推し本
前書き
テスト駆動開発(TDD)のゴール
動作する綺麗なコード
完成したかどうか分かり、バグが残っているか心配する必要がない
コードが伝えようとしていることを全て受け取れる
ユーザビリティが上がる
チームの信頼度が上がる、気持ちいいなど良いことばっかり
どうやってゴールにたどり着く?
TDDのルール
自動化されたテストが失敗した時のみ、新しくコードを書く
重複を除去する
TDDを取り入れると
テストによるフィードバックがあるので、有機的(全体としてまとまりやすい)に設計ができる
テストを書くようになる
小さな変更に迅速に応答する開発環境を備えなくてはいけなくなる
凝集度が高く、結合度が低いたくさんの部品で構成された設計ができる
パーツが多い方がテストが組みやすいから
欠陥率が下がり、先手を打って仕事できる
新規の開発がしやすい
バグ報告がなくなり、開発のマネジメントがしやすい
細かいテストごと(分単位)でコミュニケーションできる
コーディング中の不安と向き合う
難しい局面と対峙した時は、可能な限り素早く着手する
黙り込まず、コミュニケーションする
フィードバックを避けず、具体的で有用なフィードバックを探し出す
TDDを学ぶと
細かいステップで開発するようになる
TDDでうまくいかないなら、以前のプログラミングスタイルで進めれば良い
TDDはセキュリティと並行性に弱い
シンプルに物事を進められる
自動テストを書くようになる
リファクタリングで設計判断を1つずつ行える
本によると、全体を俯瞰したいならまず第III部から読めとのことなのでそこから進めてみる
第III部 TDDのパターン
第25章 テストとデータ
テストとは
どうやってするのか→自動テストを書く
テストを「する」と、「ある」は違う
自動的に実行される手順で示されるOKと、自分で画面を操作して判断するOKは違う
テストは独立させる
テストの実行は、他のどのテストにも影響を及ぼすべきではない
テストが1つ失敗したならば、問題は1つであって欲しい
テスト群の中から、いくつか気になるものを順不同で動かしたい時もあるから
コードを書く時は、紙に書き出す
必要になりそうなテストをリストする
行うべき処理などを紙に書き出す
実装しなければならない振る舞い
実装がない操作に関して、空実装として書き出す
書いたばかりのコードのリファクタリングできる部分を書き出す
テストを書いてるうちに浮かんできたテストも書く
テストファースト
テストは、テスト対象のコードを書く前に書く
実装を終えると書かないから
アサートファースト
アサーションは、最初に書く
テストはコードを書く前に書くべき。テストは、その終わりにパスすべきアサーションを書くところから始める。
こちらを優先すると、テストがかなりシンプルなものになる
テストデータ
テストファーストで扱うためのテストデータは、テストを読みやすく、理解しやすくするデータを使う
テストには読み手がいる。変数などで1や2を指定して構わない時は、1を使う
2とか、5とか意味深な値を定義すると読み手に深読みさせてしまう
例として、足し算のテストをするなら
第1引数で2を使ったなら、第2引数では3などを使った方がいい
2と2同士だとどちらの引数が使われているのか分からなくなるから
本物に近いデータを使うと、以下が捗る
外部機器からのイベントを収集して解析する、リアルタイムシステムのテスト
新システムの出力が、旧システムの出力内容と合致しているかどうかの確認(並列試験)
リファクタリング前と後で、結果が以前の結果と正確に一致するかの証明
明示的なテストを残す
テストとデータは、期待値と結果をテスト自身に含めて、明快に関係がわかるようにする
10年後にテストを読んだ人が、わかるくらいの手がかりを残す
第26章 レッドバー
リストから各テストを選ぶときの基準
書けば、動かせそうな気がするテスト
はじめのテスト
何もしないことのテストから始める
本物に近いテストを書き始めようとすると以下の問題に直面する
この機能はどこに属するべきなのか
正しい入力とは何か
その入力から得られる正しい出力は何なのか
レッド・グリーン・リファクタリングという一連の流れが大事なのだが回しづらい
始めのテストを書く時は、以下の形を作ってみる
出力と入力が完全に一致するケース
入力が可能な限り小さいものであること
code:java
public void test01() {
int sum = add(30, 70);
assertEquals(100, sum);
}
上は30と70の入力期待値が100であるが、実際sumの中身はどんなものかという意味。
これでも十分テストだ
このような形で、機能が1つか2つ必要になりそうなテストを書いてみよう
既存機能にテストを追加するなら、動作は自分で理解できているはずだ
説明的なテスト
他の人とテストコードの形で知識を共有する
チーム内で自分しかTDDしていなかったらイライラする
自分のコードで全然エラーが起きていないのがチーム内に伝わっているはずなので、説明してみよう
TDDはプログラミングの考え方のようなものなので、強要はできない
向こうのコードをレビューする時、実際にテストを組んでみてこんな感じか?みたいなので聞いてみてもいいのかも
学習用のテスト
チーム外の誰かが書いたソフトウェアのテストは、そのソフトの新機能を初めて使う時に書いてみる
例えば、S3からデータを出し入れするという挙動を実装したい
いきなり書くわけじゃなく、試しにネットで集めた情報を書いて動くか確認してから書き始めるはず
その動作確認をテストとして作ってしまえばいいのだ
既存のパッケージがアップデートされた場合にこの学習テストを走らせると、挙動の確認もできて進めやすい
技術的な脱線について
開発中に、ふと「こうすればどうなる」という開発に関係ない疑問が浮かぶことがある
ToDoリストに加えて、ヒマな時解決するようにしよう
回帰テスト
不具合が出た時は、不具合を再現させる最小のテストを書く
テストの失敗を見届ける。テストが通れば不具合も通るようになるということ。
このテストは、本来実装される時に同時に書かれておくべきであった内容になることが多い
書くべきだという内容にどうやったら気付けたのかを考える
ToDoリストに書くべき内容だったのに、粒度が細かすぎて書いていなかったとか。
うまくいかない時
休憩とる
思いつかない時は思いつかない
散歩でもなんでもする
でも書くべきものが分かっているなら、書いた方がいい
やり直す
コードを捨てて最初からやってみる
いい道具を使う
skonishi1125.iconはいいの使ってるからオッケー
第27章 テスティングパターン
大きいテストが失敗した時は
大きいテストを分割して、小さいテストにして走らせる
小さいテストがいくつかOKであれば、また大きめのテストを流す
構築する準備に手間がかかるテストを作る場合
決められた結果を返す偽物のオブジェクトを作成する
laravelなら重たいコレクションとかは、事前にデータとして作っておく的な?
オブジェクトが他のものとやりとりしていることをテストする時
テスト対象のオブジェクトが本物だと思って話している相手が実はテストケース自身だというコードを書いてみる
よくわかんないかも 時間ある時にもっかいみる
会社で聞いてもいいかも?
正しい順序でメソッドが呼ばれていることのテスト
記録用のログを作り、メソッド呼び出しのたびにログに残せば良い
エラー処理部分のコードのテスト
例えば容量がいっぱいの時のエラーなんて、滅多に見ない
その辺の実装をする時は調べて、書いて検証してみる
失敗させたままのテスト
最後に書いていたテストを失敗する状態で放置する
席に戻った時に、書き換えの内容を見て何をしようとしていたか考える
失敗したまま放置しとけば、次やる時に何からやればいいか明白になる
チーム開発なら別!きれいな状態で終えること。
第28章 グリーンバー
テストが失敗していたら
まずはベタ書きの値を返すようにして、そこから本物の式や変数に直していく(仮実装)
2+3=5を返すというようなテストでも、まず「5」でベタ打ちして結果を返すようにしてそこから修正。
第29章 xUnit
〇〇Unitとは
コンピュータプログラムの単体テスト(ユニットテスト)を行うためのテスティングフレームワークの総称
フレームワークである
関数やクラスなど、ソフトウェアの様々な要素(ユニット)をテストすることができる
PHPUnitもこれだ
アサーション
コードが実行される時、満たされるべき条件を記述して実行時にチェックする仕組み
想定した値と、実際の出力された値を比べる方式など
例えば50を返してほしい時は、50であるべきとアサーションの中で記述する
javaならassertEquals(50, rectangle.area())みたいな感じ
この辺の話は実際に使ってからじゃないとわからないかも・・・
テストメソッドについて
命名規則としては、testから始めるというのが一般的
混乱せずに読めるテストコードは、3行が目安
このくらい小さな枠組みで書いていく
言葉で書き始めて、そこをテストにしていく
例外処理のテスト
期待される例外を想定したテストを書く
その例外が発生しなかったならテストが失敗するように書く
code:php
try {
メソッド1
fail(); // メソッド1から例外が発生しない時、failさせる
} catch {
....
まとめてテスト
1つの実装について書いたテストをまとめたものを作る
それをさらにまとめて、全体のテストをまとめる
第30章 デザインパターン
様々なプログラムで再利用できる、汎用的な設計パターンのこと
GoFデザインパターンがメジャーだ
TDDの場合は趣向を変える必要がある。でもGoFを知ってないとわからなさそう
わかったら読み直す
リファクタリング
リファクタリングすると、すでに通っていたテストが動かなくなる可能性がある
ベタ書きの値を変数に変えるという作業も、リファクタリングだ
メソッドも細かく分ける
テストが独立して実装できるようになるから
名前を考えてあげよう
この辺も実際やらないとわかんないかも
TDDを身につける
1歩の大きさ
各々のテストがどの程度カバーするのか
リファクタリングの過程で休憩地点をどの程度作るのか
どうしてもプロジェクトにおいて大きいテストもできるので、大小どちらも対応できるようになろう
いいテスト
テストの前準備がめちゃくちゃ長いコードは、そもそも元がおかしいので分割すべき
時間がかかりすぎるテストはやらなくなるので避けたい
どの程度テストをかくか
三角形の辺を3つ入れて、それぞれの場合のデータを返すものがあるとする
これにテストを実装する数は、まあ人によってまちまち
自分の経験と省察から判断しないといけない
テストを消す時
2つのテストの間に重複が起こった時
テストを消すことでシステムの振る舞いに自信がなくなるのなら、消すべきではない
異なるシナリオを想定して作られているような場面を想定できれば、消すべきではない
逆にこれ以外はいらない方を消そう
プロジェクトの途中からTDDに乗り換えるには
テストのことを考慮していないデータは、テストを書きにくい
リファクタリングする?
失敗が待っているし、その失敗する具体的な内容もわからない・・・
全てにテストを書いていく?
新しい機能も増えないし、時間もかかる
変更のスコープを狭める
変えたらスッキリしそうな部分があっても、とりあえず無視
テストとリファクタリングのデッドロックを解消する
複数の実行中のプログラムなどが互いに他のプログラムの結果待ちとなり、待機状態に入ったまま動かなくなる現象のこと
ペアで確認しあってやっていくなど。
進めていくと、よく改修のある部分はテスト駆動で回っているようになってくる
その時はスピードを下げ、デットロックを解消していこう
第III部完
実際にLaravelでphpunitを使ってテストを書いてみよう
Laravelに標準搭載されてるらしい
第I部
1.仮実装
機能を実装させる時
todoリストを作って、数えられるような形でタスクを積む
細かなステップを踏む
2.明白な実装
TDDのサイクル
テストを書く
動かす(固定の値を返すようにしたりして、とにかく動かす)
正しくする(ずるい手を使ってしまったので、正しく動作するようにする)
3.三角測量
Value Objectパターン
オブジェクトの中で状態を設定したら、returnとして新しいオブジェクトを返すようにするという考え方
変数を使い回す時、その変数の値が書き変わっている不具合を防ぐ考え方
三角測量
関数は「二つ以上の例がある時、一般化」させる
実例を二つ定義してみるのが三角測量。
code:java
# 固定値でとりあえず返すように定義された関数
assertEquals(4, plus(3, 1));
private int plus(a, b) {
return 4
}
例をもう一つ作れるかチェック
code:java
# 三角測量
assertEquals(4, plus(3, 1));
assertEquals(7, plus(3, 4));
作れたらこのplus()関数は一般化できるので、調整する。
code:java
# 一般化する
private int plus(a, b) {
return a + b;
}
4.意図を語るテスト
テストがどういったことをしたいのか、より明快にしていく
特定の数aとbを足した数を出したいとか、そういう考え方に沿ってコードを整える
5.原則を破る
新しい別の項目をチェックする時
コピペで既存コードと同じ設計を貼り付けて調整する
本の例だと、Dollarというドル用のクラスがある
これに加えてフランスの通貨のFrankというクラスを使った
中身はDollarのほぼコピペ
同じコードは統一した方がいいのでこれは好ましくない方法
だが、テスト駆動はまず「動かす」ところから始めるのでこれから修正していけばOK
出てきた重複は動かしたのちに、リファクタリングとして対処しよう
6.テスト不足に気づいたら
重複を削減する
DollarとFrankの親クラスMoneyを使えば、色々重複排除できそうなので本では行っている
テスト不足があった場合
なかったら作ればOK あればよかったと思うテストを書く
書かないとリファクタリングの過程で壊れてしまう
7.疑念をテストに翻訳する
DollarとFrankの比較
本では、同じレートで検証しているのでドル=フランのレートになっているかのチェックが必要
そう思ったなら、テストを作ってチェック
8.実装を隠す
DollarとFrankのサブクラスの存在をMoneyの親クラスで隠している。
依存性が少し薄れたらしい。
9.歩幅の調整
サブクラスの重複をさらに消す
どうやら消した方がいいらしい
「どうやって〇〇を実装すればいいか」は、「どのように〇〇をテストしようか」と最初に考える
例えば通貨の単位を実装したい時
Dollarでの定義を以下とする
currency = "USD";
Frankでの定義を以下にする
currency = "CHF";
こうすることで、親のMoneyクラスで一括で管理できることがわかった
2つ以上の例をだして、三角測量している
親で一括管理(一般化)して重複を消すことができる。
テストを書いてる途中に、別のテスト部分を直したくなった時
ちょっとだけなら一緒にやっちゃおう
本当にちょっとだけ
さらに割り込みが見つかった時は一旦置いておく
歩幅の調整
小さなステップが窮屈ならば、歩幅を大きくする
不安を感じるのならば、歩幅を小さくすれば良い
10.テストに聞く
より一般化したい部分の道筋を見極めるには
あえてリファクタリングした部分をもとに戻してみる
一般化した部分をまた二つの例に戻すとか
その一般化は、本当に必要か考える
今回、クラスが同じであるかどうかのリファクタリングをしていた
けど、実際理解したいのは「通貨が等しいかどうか」なので、それがわかればいい
エラーでこけたら、エラーについてのテストを書く
例えばa / bをしたくて、b = 0だった場合はゼロ除算エラーが出る
問題は aかbが0の可能性があるということなので、それをチェックするテストを作るというような形。
11.不要な実装は消す
コードをリファクタリングしているうちに不要になってくる部分がある
それは今までやってきたように、どんどん消してあげる
消していく中では、テストを実行して通ることを確認しながら消していく。
12.設計とメタファー
todoリストが大きい問題と小さい問題でごちゃついてきたら
未解決の問題を別のリストに移すなど大まかに分ける
5ドル + 5ドル = 10ドルになることをテストする時
これもテスト
code:java
public void testSimpleAddition() {
// Moneyオブジェクトのsumに、 5ドル + 5ドルをplus()関数で+する
Money sum = Money.dollar(5).plus(Money.dollar(5));
// Moneyオブジェクトのdollarの10(10ドル)と、今計算したsumとが同じであるかのチェック
assertEquals(Money.dollar(10), sum);
}
メタファーとは
用語に使われる言葉の、元々の意味から特徴を理解すること
例えば、 Ark というバックアップツールがある
Arkは、方舟という意味
ノアの方舟→ノアが洪水から家族たちを救うために作った船
なので、救うというメタファーであるArkという名前がつけられている
みたいな感じだ
13.実装を導くテスト
この辺はもう少しプログラムわかるようになったら、改めてみてみるといいかも?
14.学習用テストと回帰テスト
作り出した値でどこまで表現できるかとか、そういった事象を検証する場合は学習用テストを作る。
エラーが発生した時、その状況を再現する回帰テストを書く。
15.テスト任せとコンパイラ任せ
16.読み手を考えたテスト
テストを書く時、読者のことを考える
TDDはテストコードとプロダクトコードの行数は同じくらいになる。
通常の2倍の速度で書くか、半分のコード行で実装しなければ恩恵を受けられない。
実際に測ってどの程度の差異が発生するのか確認してみても良い
17.振り返り
todoリストが空になったならば、設計を見直す
テストは足りているか
「こう動いてはいけない」というテストを書いたら、実際は対象がそのように動いてしまう場合もある
そうなったら、なぜ動いたかを調査するタイミングになる
テストをきれいに機能させるアプローチを意識する
仮実装、三角測量、明白な実装
明白な実装とは、頭にシンプルな操作の実装が思いついた時はそのまま書いてしまうこと。
2+3=5というコードを書きたいなら、三角測量などをしてもいいが、そのまま実装してしまう。
ただしレッドバーが出てしまったら、改めて歩幅を縮めること。
Javaのコードで書かれていたが、PHPに直して書いてみる・・・?
第II部 xUnit
この章ではPythonを使って、テストフレームワークを自作している
テスティングフレームワークを触る時があったら読み返すといいかも...