今回の記事では、読みやすい単体テストを書くコツ について書こうと思います。
対象読者層
- 単体テスト(テストコード)を書き始めたばかりの方
- 単体テストを何から書けばよいのかわからない方
- 単体テストのコードが複雑で可読性を上げる方法を模索している方
読みやすい単体テストを書くコツ
AAAパターンを意識しよう
AAAパターンとは、単体テストの構造を意味しています。
それぞれの意味は以下。
- 準備(Arrange)
- 実行(Act)
- 検証(Assert)
つまり、すべての単体テストは、準備→実行→確認 の順にフェーズが進んでいきます。
BMIで肥満かどうかを診断するメソッドに対するテストコードを例にしてみましょう。
(BMIは[体重(kg)]÷[身長(m)の2乗]で求めることできます。)
テスト対象コード
public class BMICalculator {
public static String diagnosisBMI(double weight, double height) {
// 身長をメートルに変換して2乗する
double bmi = weight / Math.pow(height / 100, 2);
if (bmi < 18.5) {
return "痩せすぎ";
} else if (bmi >= 18.5 && bmi < 25) {
return "標準";
} else if (bmi >= 25 && bmi < 30) {
return "肥満(軽度)";
} else {
return "肥満(中度以上)";
}
}
}
テストコード
import org.junit.Test;
import static org.junit.Assert.assertEquals;
public class BMICalculatorTest {
@Test
public void test_diagnosis_normal() {
// 準備(Arrange)
double weight = 60; // kg
double height = 170; // cm
// 実行(Act)
String result = BMICalculator.diagnosisBMI(weight, height);
// 検証(Assert)
assertEquals("標準", result);
}
}
AAAパターンを用いる事で、全てのテストで構造が統一化され可読性の向上に繋がります。
可読性が向上すれば、テスト全体の保守コストを下げることにも繋がります。
避けるべきこと:1つのテストメソッドで検証するテストは1つまで
1つ目の「AAAパターンを意識しよう」とも繋がりますが、1つのテストメソッドで検証する項目は1つまでです。
つまり、1つのテストメソッドでは「準備→実行→検証」フェーズで収めなければなりません。
(検証フェーズ終了後に次のテストのための準備フェーズに移ってはいけない)
これは、テストコードの複雑化を防ぐためです。
サンプルコードを例にすると、1つのテストメソッドで標準体型、痩せすぎ、肥満のパターンを検証をしてはいけません。
検証するパターンごとにテストメソッドを作成しましょう。
実行フェーズが1行を超える場合は要注意
この場合、テスト対象コード設計に問題がある可能性が高いです。
(実装者が2回以上の操作(メソッドの呼び出し)を行わないと、目的が達成できない設計になっている)
実行フェーズのコードは1行で収まるように、テスト対象コードのリファクタリングを行ってください。
テスト対象クラスのインスタンス名は「SUT」と命名しよう
SUTとは、System Under Test(テスト中のシステム)の略称です。
テスト準備フェーズの中でテスト対象クラスのインスタンスを生成する際、
変数名を「sut」命名で統一することで、どのクラス(インスタンス)がテスト対象なのかがわかりやすくなります。
TestClass sut = new TestClass();
String result = sut.testMethod();
テストメソッド名の決め方について
以下の指針を意識することで、表現に富んだ読みやすいテストメソッド名をつけやすくなります。
- 厳密な命名ルールを作らない
- 非開発者に対してどのような検証をするのかが伝わるような名前を付ける
(ここで言う非開発者とは、実装の詳細を知らないビジネスユーザーなどのこと) - アンダースコア「_」で単語を区切るようにする(英語の場合)
実際に上記のルールに従って、最初のサンプルコードであるtest_diagnosis_normal
を改名してみましょう。
改名後のテストメソッド名
// 身長に対して適切な体重が設定されると、標準体型と診断される。
@Test
public void appropriate_weight_diagnosis_for_a_standard_body_type()
以下の点に注目してください。
- 検証内容を英文でテストメソッド名に表現することで、非開発者にも伝わるようになっている
- テスト対象のメソッド名(diagnosisBMI)が、テストメソッド名に含まれていない
厳密なテストメソッド名のルールを設けず、非開発者に検証内容が伝わるメソッド名を心がけましょう。
テスト対象のメソッド名は、テストメソッド名に含めないほうが良いです。
理由は、単体テストはコードをテストしているのではなくシステムの振る舞いをテストするのが目的だからです。
サンプルコードであるBMICalculator.diagnosisBMI()を例にすると、
テスト対象のメソッド名(diagnosisBMI)はテストに関係なく、
BIMから肥満度を判定する という振る舞い部分が重要だからです。
例えば、diagnosisBMI()のメソッド名を別の名前に変えても、BIMから肥満度を判定する という振る舞い部分は変わりません。
しかし、テスト対象のメソッド名をテストメソッド名に含めてしまっている場合、テストコードも修正が必要になります。
つまり、振る舞いではなく、コードとテストが結びついている状態になってしまいます。
コードとテストが結びついている状態では、テストの保守性が悪くなってしまいます。
「〇〇メソッドのテストをする」ではなく 「〇〇するという振る舞いをテストする」というマインドが、テスト対象のメソッド名をテストメソッド名に含めてはいけない理由の根幹にあります。
最後に
可読性の高いコードを書く方法についてはたくさんの情報が溢れていますが、単体テストに関してはあまり無いような気がします。
僕自身、テストコードを書き始めて2年くらい どういうふうにテストを書くのが正解なのか ずっと悩んでいました。※今も模索中ではありますが(汗)
テストの書き方については、今後も発信していけたらと思います。
ここまで読んでいただきありがとうございました。