こんにちは。
TECHSCORE BLOG初参戦の中西です。
これは😺TECHSCORE Advent Calendar 2018😺の16日目の記事です。
皆さんはテスト駆動開発という言葉をご存知でしょうか?
開発に携わっている方は、一度は耳にしたことがある言葉だと思います。
今回は新人の私がテスト駆動開発を一から学ぶにあたり、基本的なところですが調べたことをまとめてみました。
これからテスト駆動開発について調べる人の参考になれば幸いです。
テスト駆動開発を学ぶきっかけ
私は開発の新人として、ソフトウェアのテスト業務から始めました。
テスト業務に関わる中で、得た知識を開発で活かせないかと考えるようになりました。
そこで先輩からアドバイスされたのが、テスト駆動開発です。
自分が身に着けたテストの知識を開発に活かせるこの方法は、私にとってまさに理想的な存在で、
調べてみようと強く思い、この記事を書くことにしました。
テスト駆動開発について
テスト駆動開発は「テスト」と名前が付いていることからソフトウェアテスト手法のように感じられますが、実際はソフトウェアの開発手法の一つです。
このテスト駆動開発ですが「テストファースト」という考え方が基になっています。
以下がテストファーストの考え方と特徴です。
-
考え方
・プロダクションコードを書く前に、そのプロダクションコードについてのテストコードを作成する。
特徴
・テストコードを初めに書くため、プロダクションコードが持つ役割をはっきりとさせやすい。
・テストコードとプロダクションコードがセットになっているため、プロダクションコードの修正時にそれに対応するテストコードを再利用できる。
・テストコードを書いてからそれに通るようなプロダクションコードを書く必要があるため、単体テストの回数を増やすことができバグが少なくなる。
テスト駆動開発は上記の考え方と特徴を受け継ぎつつ、より実践的かつ洗練されたものとして開発現場で利用されています。
テスト駆動開発では、以下の工程を上から順に繰り返して開発を進めることが原則となっています。
- 失敗するテストコードを書く
- テストコードを通過するプロダクションコードを作成する
- リファクタリングをする
失敗するテストとは?
テスト駆動開発で初めにする工程が、失敗するテストを書くことです。
私の経験にはなりますが、三つの工程の中で理解が最も難しかったと言い切れる工程です。
当初、文面をそのまま受け取っていた私は、
「エラーパターンを確かめるテストを、プロダクションコードの実装前に書く」
として、あれやこれやとエラーパターンを考えてテストを作成していました。
ですが、このことはテスト駆動開発で求められている「失敗するテストを書く」こととは少し意味合いの違うものでした。
正しくは
- プロダクションコードを「実装をする前に」テストコードを書き、実行する
ということです。
実装する前にテストコードを実行すると、当然のことですが実装されていないプロダクションコードについてのテストコードなので失敗します。
これがテスト駆動開発の第一段階である「失敗するテストを書く」ということです。
テストコードを書くためには、要件を十分に理解した上で実装に落とし込む必要があります。
つまり、テストコードは実行できる設計と言い換えることもできます。
テスト駆動開発を実践してみた
ここからは、実際にテスト駆動開発の手法に従って開発をしてみます。
【開発環境・使用言語】
・Java 1.8.0
・JUnit 5
・Eclipse IDE
-------------【失敗するテストを書く】-------------
前述した内容を念頭に、失敗するテストコードを書きます。
まずは実装したい機能を考える必要があるので、今回作るものは以下の要件とします。
要件:金額が入力されると、消費税が課税された金額が出力される。
上記の要件に従いテストコードを作成します。
1 2 3 4 5 6 7 8 9 10 |
import static org.junit.Assert.*; import org.junit.Test; public class CashTest { @Test public void testCalc() { assertEquals("TEST FAILED", 108, Cash.calculate(100)); } } |
上記はCash.calculate()のテストコードです。
引数に100を渡し、戻り値が108となることを検証します。
※assertEquals()の第一引数は失敗時に出力されるメッセージです。
このときCash.calculate()は未実装なので、以下の仮プロダクションコードを作成します。
1 2 3 4 5 |
public class Cash { public static int calculate(int num) { return num; } } |
入力された数字をそのまま返すだけのプロダクションコードです。
※金額計算には BigDecimal を使用するのが一般的ですが、今回は可読性を優先し int で実装しています。
プロダクションコードとそれに対するテストコードのセットができたので、テストを実施します。
Eclipseでテストコードを実行すると上記のように表示されます。
ここで注目していただきたいことは以下の二点です。
- テストが失敗しているということ
この時点で成功していると正しくテスト駆動開発ができていないということになりますので、テストコードもしくは仮のプロダクションコードが間違えている可能性があります。 - 赤いバーが表示されているということ
JUnitを初めとするテストフレームワークで共通して見られるエラー表示です。この色にちなんで、テスト駆動開発ではこの工程のことをRedと表現しています。
テストコードが失敗したので次の工程に移ります。
-------------【テストを通過するプロダクションコードを書く】-------------
失敗するテストコードを作成し失敗させた後には、そのテストコードが成功するようにプロダクションコードを作成します。
ここでのポイントはできる限りすばやく作成し、最小限の実装にするということです。
この考え方はテストファーストというよりも、テスト駆動開発もその一種として含まれるアジャイル開発の性質を反映しているものです。
できるだけ小さな実装を繰り返し、確実に実装していくことで結果的に手戻りの少ない開発になるという効果が存在します。
それでは、先ほどのテストコードが失敗したプロダクションコードを修正します。
できるだけ早く書き単純な実装となるように心がけました。
1 2 3 4 5 6 |
public class Cash { public static int calculate(int num) { int fee = (int) (num * 1.08); //サンプルコードのため切り捨てられる小数は考慮しない return fee; } } |
受け取った数値に対して、消費税の8%を課税して返すものです。
ポイントとして見ていただきたいのは、クラスやメソッドの名前は一切変えていない点です。
ここがテスト駆動開発でもポイントとなっていて、修正前後で同じテストコードを利用するため必然的に呼び出している名前を変えられないようになっています。
それではテストコードを実行します。
エラーが出ず成功しましたので、このテストコードは合格、すなわち実装したいプロダクションコードが正しく実装できました。
このとき、先ほどのRedとは異なり緑色のバーが表示されているため、この工程のことをテスト駆動開発ではGreenと表現しています。
本来ならばこの工程の後にリファクタリングを実施しますが、今回は実装箇所が少ないため省略します。
まとめ
今回はテスト駆動開発の工程と簡単な例を紹介しました。
作成したテストコードとプロダクションコードは以下になります。
テストコード
1 2 3 4 5 6 7 8 9 10 |
import static org.junit.Assert.*; import org.junit.Test; public class CashTest { @Test public void testCalc() { assertEquals("TEST FAILED", 108, Cash.calculate(100)); } } |
プロダクションコード
1 2 3 4 5 6 |
public class Cash { public static int calculate(int num) { int fee = (int) (num * 1.08); //サンプルコードのため切り捨てられる小数は考慮しない return fee; } } |
今回は一回のサイクルでしたが、テスト駆動開発を利用することでバグの少ないプロダクションコードを書くことができる、ということが感じていただけたのではないでしょうか?
テストコードを先に書くことでシステムへの理解が深まる、という特徴が自分に合っていると感じています。
大きなシステムであるほど大きな効果が得られるので、実際のシステム開発でも積極的にテスト駆動開発を実践していきたいと思います。
そのときにはまた記事にしますので、ご一読いただけると幸いです。
それでは、読んでいただきありがとうございました!