第1部 単体(unit)テストとは
第1章 なぜ単体(unit)テストを行うのか
単体テストを行う目的は、ソフトウェア開発の成長を持続可能なものにするため。
開発スピードが急に落ちる現象はソフトウェア・エントロピーの増加によって起こる。
網羅率に関する問題
コード網羅率は次のように計算される。
\displaylines{
コード網羅率(テスト網羅率) = \frac{実行されたコード行数}{総行数}
}
public static bool IsStringLong(String input)
{
if (input.length > 5)
return true;
return false;
}
public void Test()
{
bool result = IsStringLong("abc")
Assert.Equal(false, result);
}
コード網羅率は 4/5 = 0.8 = 80%となる。
public static bool IsStringLong(String input)
{
- if (input.length > 5)
- return true;
+ return input.length > 5 ? true: false;
}
public void Test()
{
bool result = IsStringLong("abc")
Assert.Equal(false, result);
}
このようにすると、コード網羅率は100%に変わるが、テストの質は改善されておらず意味がない
コード網羅率は行数しか見ていないため、コード網羅率が高くてもテストの価値が必ずしも高いわけではない。
コード行数ではなく分岐(if文やswitch文)を計測する分岐網羅率がある。
コード網羅率より正確な網羅率の計測ができる。
\displaylines{
分岐網羅率 = \frac{経由された経路の数}{分岐路の総数}
}
Googleのテストブログでカバレッジについて言及があります。
A high code coverage percentage does not guarantee high quality in the test coverage. Focusing on getting the number as close as possible to 100% leads to a false sense of security. It could also be wasteful, burning machine cycles and creating technical debt from low-value tests that now need to be maintained.
コード カバレッジのパーセンテージが高いからといって、テスト カバレッジの高品質が保証されるわけではありません。数値を 100% にできるだけ近づけることに重点を置くと、誤った安心感が生まれます。また、無駄が発生し、マシンサイクルが無駄になり、維持する必要がある価値の低いテストによる技術的負債が生じる可能性もあります。
網羅率はテストコードの質が悪いことを判断できるが、質が良いことを判断するのには向いていないため網羅率はテストスイートの質を良くするためのヒント程度で考えておく。
何がテストスイートの質を良くするか?
- テストすることが開発サイクルの中に組み込まれている
- コードベースの特に重要な部分のみがテスト対象となっている
- 全てのコードが単体テストする価値を持っている訳ではない
- 単体テストにかける労力をシステムにとって重要な部分(ドメインモデル)に向ける
- ドメインモデルではないコード
- インフラに関するコード
- 外部サービスや依存関係にあるもの(DBなど)
- 構成要素同士を結びつけるコード
- ドメインモデルではないコード
- 最小限の保守コストで最大限の価値を生み出すようになっている
まとめ
- 単体テストを行う目標は、ソフトウェア開発に関するプロジェクトの成長を持続可能なものにする
- 網羅率の値に縛られることはソフトウェア開発において害になることがある
- システムの核となる部分に対して高い網羅率を出せることは良いことだが、そのことを強制することは開発の妨げとなる
- 優れたテスト・スイートの特徴
- テストすることが開発サイクルの中に組み込まれている
- コードベースの特に重要な部分のみがテスト対象となっている
- 最小限の保守コストで最大限の価値を生み出すようになっている
第2章 単体テストとは何か?
単体テストの定義は次の通りである。
- 単体(unit)と呼ばれる少量のコードを検証する
- 実行時間が短い
- 他のテストケースから隔離された状態で実行される
「単体」と「隔離」の解釈には2つの学派が存在しており、本書は古典学派を採用している。
隔離対象 | 単体の意味 | テスト・ダブルの置き換え対象 | |
---|---|---|---|
ロンドン学派 | 単体 | 1つのクラス | 不変依存を除く全ての依存 |
古典学派 | テストケース | 1つのクラス、もしくは、同じ目的を達成するためのクラスの1グループ | 共有依存 |
まとめ
- 単体テストの定義は次の通りである
- 単体(unit)と呼ばれる少量のコードを検証する
- 実行時間が短い
- 他のテストケースから隔離された状態で実行される
第3章 単体テストの構造的解析
AAAパターン
-
準備(Arrange)フェーズ
- テストケースの事前条件を満たすようにテスト対象システムとの依存の状態を設定するフェーズ
-
実行(Act)フェーズ
- テスト対象システムのメソッドを呼び出すことで、テスト対象の振る舞いを実行させるフェーズ
- 実行結果を確認するときに使えるように保持しておく
-
確認(Assert)フェーズ
- 実行結果が想定した結果であることを確認するフェーズ
- 確認結果が戻り値のこともあれば、テスト対象システムや協力者オブジェクトのメソッドが呼び出されたことが確認対象の実行結果となることもある
public class Caculator
{
public double Sum(double first, double second)
{
return first + second
}
}
public class CaculatorTest
{
[Fact]
public void Sum_of_tow_numbers()
{
// 準備フェーズ
double first = 10;
double second = 20;
var calculator = new Caculator();
// 実行フェーズ
double result = calculator.Sum(first, second);
// 確認フェーズ
Assert.Equal(30, result);
}
}
Given-When-Thenパターン
- Given(前提として) -> AAAパターンの準備フェーズに該当
- When(⚪︎⚪︎したとき) -> AAAパターンの実行フェーズに該当
- Then(その結果) -> AAAパターンの確認フェーズに該当
まとめ
- 単体テストは次の3つのフェーズで構成される(AAAパターン)
- 準備(Arrange)
- 実行(Act)
- 確認(Assert)
第2部 単体テストとその価値
第4章 良い単体テストを構成する4本の柱
良い単体テストを構成する柱とは、次の4つの性質
-
退行(regression)に対する保護
- テストすることで退行(バグ)の存在を検知できるかを示す
-
リファクタリングへの耐性
- 偽陽性(false positive)を生み出すことなくプロダクションコードをリファクタリングできるのか示す
- 単体テストにおいて偽陽性とは、機能自体は問題ないのにも関わらず、テストが失敗すること
- 偽陽性がテストスイートに与える影響
- 開発者は嘘の警告に慣れてしまい注意を払わなくなる
- テストを信頼できるセーフティネットとして見ることができなくなり、テストへの信頼が失われる
- 偽陽性(false positive)を生み出すことなくプロダクションコードをリファクタリングできるのか示す
-
迅速なフィードバック
- テストの実行時間がどのくらい短くなるかに影響する
-
保守のしやすさ
- 何のテストをしているのか理解できるか?
- テストケースはサイズが小さいほど、読みやすくなる
- テスト実施することがどのくらい難しいか?
- テスト時に使われるプロセス依存が少ないほど、テスト実施が簡単いなる
- 何のテストをしているのか理解できるか?
第5章 モックの利用とテストの壊れやすさ
モックとスタブの違い
モックとスタブは、単体テストで使用される2種類のテストダブルのこと。
-
モック
- テスト対象のシステムから依存に向かって行われる外部に向かう出力を模倣し検証するのに使われる
-
スタブ
- テスト対象システムに向かって行われる内部に向かう入力を模倣するのに使われる
- スタブとのやりとりを検証することはテストを壊れやすくすることに繋がる
- スタブは結果の一部となるものではなく、最終的な結果を生み出す過程の中で使われるもののため
第6章 単体テストの3つの手法
- 出力値ベーステスト
- テスト対象システムに入力値を与え、そこで生成された結果を検証すること
- 状態ベーステスト
- テスト対象システムと協力者オブジェクトの状態を検証すること
- コミュニケーションベーステスト
- モックを使ってテスト対象システムと協力者オブジェクトのコミュニケーションを検証すること
出力値ベーステスト | 状態ベーステスト | コミュニケーションベーステスト | |
---|---|---|---|
リファクタリングへの耐性を維持するためのコスト | 低い | 普通 | 普通 |
保守しやすさを維持するためのコスト | 低い | 普通 | 高い |