これはなに?
- ROXX社の社内勉強会にて開催した、テスト駆動開発のワークショップのためのスライドです
- 割と筆者の主観が入りまくっています
- 某ライオン氏のワークショップに比べれば品質は悪いと思いますので、使う場合はその辺ご了承ください
お品書き
- そもそもテストって何?
- テスト駆動開発とは何か
- テスト駆動開発をやってみよう
そもそもテストって何?
ソフトウェアテスト
- 作成したソフトウェアの正しさを、実際の動作とその出力を通して確認すること
- 手動テストと自動テストがある
- エンジニアの文脈では自動テストで話す場合が多い
自動テスト
- 何らかの実装ないし仕様書をもとに、機械が実行するテスト。
- 機械が実行するので、テスト中も人間は別の作業をすることができる
- 機械が実行するので、同じテストを(ほぼ)同じように実行できる
なぜ自動テストをするのか
- https://qiita.com/niisan-tokyo/items/ff793e271fd35b1a20bb
- 常にテストを実行することで、既存の実装が破壊されていないことを確認できる
- 既存の実装のすべてが破壊されていないかどうかを、新しい実装ごとに手動確認するのは現実的に不可能であり、自動テストを使うしかない
- つまり、運用しつつ開発を続けるソフトウェアにおいて、自動テストは不可欠
改築したら別の個所がぶっ壊れた。
テストで防ぎたいのはこういうやつ。
自動テストは難しいのか
- https://qiita.com/niisan-tokyo/items/cf445985f951291a1b34
- テストコードは
print
やecho
が書ければ誰でもできる- つまり、むしろ簡単だ!
- むしろ、新人さんにはまずテストから教えるべき
- テストコードを作るのは誰だってできるけど、テストする環境を用意するのは別の話
- ルールに従うのではなく、ルールを作る側と言えばわかりやすいかも?
テストの手順
基本的にどのテストも、以下のように書かれる
public function test_something()
{
Config::set('number', 1);// 前提条件の設定(ない場合もある)
$object = new Something;
$result = $object->run();// テスト対象を実行する
$this->assertEquals(1, $result);// 結果の検証
}
流れが決まっているため、テストを書くときは基本的に悩まずに書くことができる
実装とテストの関係性
- テストが通っている限り、実装を変更することができる
- いわゆるリファクタリング
- 実装が変わっていなければ、テストの修正・追加をすることができる
- テストコードのメンテ(個人的にあまり必要ないとは思う)
- 片方を固定することで、もう片方の変更を可能にする
- 双対性というらしい
テスト駆動開発
テスト駆動開発とは?
- テストを、仕様の確認やリグレッションのためのツールとしてだけではなく、開発の促進にも利用してしまおうという開発手法
- TDD(Test Driven Development)などと略される
- きれいなコードに仕上げたいという目的がある
テスト駆動開発の手順
- TODOリストを作成する
- TODOリストから一つ選んで、テストを作成し、失敗することを確認する(RED)
- テストが通るように最小限の実装をして、テストが通るようにする(GREEN)
- テストが通る状態を維持したまま、コードを整理し、きれいな状態にする(REFACTORING)
- 2~4を繰り返し、TODOリストがなくなったらタスクを完了とする
リファクタリング
- 実装を、その振る舞いを変えずにコードを整理して、わかりやすく拡張性がいい感じに改善すること
- 現時点での実装に対し、自分の考えうる最良の実装にしてやるといい
- ここが設計の頑張りどころ。つまり、設計は後からやってくる
- 新規の実装だけでなく、「修正」のタスクでももちろんやるべき。つまり、昔のコードに手を入れる時でも遠慮なくやる
テスト駆動開発の利点
- タスクのゴールが明確化する
- TODOリストを作ったのだから、それを全部こなせば、それすなわちタスクの完了である
- 実装開始が早い
- 経験的に、具体的な実装が存在している方が、課題にしろ困難にしろ見つけやすい。どんなに汚くても、早めにモノを用意しよう
- わからない箇所、難しい箇所を特定できる
- TODOリストをこなすなかで、止まった地点が難しいところである。知恵を絞り、仲間と協力して難所をくぐり抜けよう
- リファクタリングにより最終的にきれいなコードを提供できる
- 動くコードはいわゆるたたき台である。これを元にして良いものに仕上げよう
- 過去のコードでも修正タスクでリファクタリングすることで、常にコードの改善を進めていける
- 昔の恥ずかしいコードを見つけちゃっても、さっさと直そう。テストがあり、動作が保証されているのだからこそ、それが許される
テスト駆動開発とペアプロの相乗効果
- TODOリストをお互いに確認し合える
- 自分一人ではなく、ほかのメンバーと一緒決めるので、より信頼できるゴールだといえる
- 難しい部分の実装が出てきた時に、相談し合える
- ヘルプを求めたいと感じた時、すぐそこに相棒がいるのだから、相談も捗るというもの
- リファクタリングでお互いの方針をぶつけ合い、よりいい感じの設計へと導ける
- お互いがいいと思うモノを合わせることで、より良い実装へと昇華させよう
FizzBuzzによるテスト駆動開発の例
TODOリストを作る
PHPのテストの書き方はわかっているので、TODOリストをメソッド形式にして書き出す。
FizzBuzzTest.php
<?php
namespace Tests;
use App\FizzBuzz;
use PHPUnit\Framework\TestCase;
class FizzBuzzTest extends TestCase
{
private FizzBuzz $obj;
public function setUp(): void
{
$this->obj = new FizzBuzz;
}
/**
* @test
*/
public function 値に1をいれたら1を返す()
{
}
/**
* @test
*/
public function 値に2を入れたら2を返す()
{
}
/**
* @test
*/
public function 値に3をいれたらFizzを返す()
{
}
/**
* @test
*/
public function 値に6をいれたらFizzを返す()
{
}
1を入れると1を返すテストを書いて失敗する
ちょっと見ずらいけど、$this->assertEquals('1', $this->obj->run(1));
って書いてある
何も実装していないので、動くわけがない。
動くコードにする
動くコードを作る
最小限のもっとも単純なコード。
これで正しいのだ。
2を入れたら2を返してくれるか?
動くコードにする
自分自身を返すのが手っ取り早いので、それでやる。
例題が簡単すぎるので、リファクタリングをする必要に駆られない。。。
3を入れるとFizzが返る
まあ、動きません。
もちろん、これまでの実装は生きているので、一つ目と二つ目のテストは通っている
テストを通すように実装
繰り返していって
TODOは全部終わった
でも、きれいなコードとはいいがたい。
リファクタリング
3行ぐらいに押し込んでみる
テスト通っているから問題ない。
テスト駆動開発をやってみよう
テスト駆動開発をやってみる
- TDDを実践してみて、どういうものかを体験してみよう
- 今回、複数の言語で、最低限テストを書ける環境を用意しているので、参加者の好きな言語での実装が可能である
- また、今回は基本に忠実にをモットーに、以下のことを実践しよう
- テストコードにおいて、基本的に1メソッド1アサーション
- テストファースト
- ピンポンプログラミングを試してみよう
- 開発者A, Bでペアプロする時
- AがTODOの中でテストを作り、そのテストが失敗することを確認する
- Bがそのテストが成功するように最小限の実装をする
- A,Bで相談し、リファクタリングを実施する
- 1~3を繰り返す。繰り返すごとにA,Bの役割を反転させる
- 開発者A, Bでペアプロする時
- 全力でリファクタリングしよう
ピンポンプログラミング
まずTODOリストを作ろう
- 与えられたお題のゴールを見定めよう
- いつまでも考えられてしまうので、タイムボックスを定めるとよい
片方がまずTODOの中から一つ選んでテストを書く
- もし書きにくそうにしていたら、もう片方がサポートしよう
- スピードを意識してみよう
もう片方が実装する
- 最小の実装を心掛けよう
- こちらもスピードを意識しよう
リファクタリングをする
- リファクタリングはペアで協力して行う
- 相談をして、「現仕様を変えずに」「コードをきれいに」する
- リファクタリングが必要なければ、「リファクタリングの必要なし」というけつろんをくだしてさきにすすめよ
- 簡単そうなテストからどんどん作っていこう
- 最小限の実装するフェーズでは、とにかく速度を優先しよう。こだわりは、リファクタリングフェーズで!