🧘

ユニットテストの流派

2022/07/02に公開

1. BDDとは

BDD(ビヘイビアー駆動開発) はTDDを拡張した手法です。

1つのシナリオを書き、そのシナリオを満たすように実装を進めていきます。つまり、ユニットテストの範囲は単一クラスではなく、複数の依存関係にあるクラスも含みます。

基本的にビジネスマン(非エンジニア)から見たとしても理解ができるテストであり、受け入れテストとも言われます。
詳細に言うと、受け入れテスト(ATDD)はユーザー寄りで、振る舞い駆動開発(BDD)は開発者寄りと見る人もいます。

どちらかというとUIテストに使われる傾向があり、「Given-When-Then」パターンなどが手法として有名どころです。

また、ツールとしてはRSpec, Jest等が有名です。

2. ユニットテストの考え方と派閥

2.1. ロンドン派

  • テスト対象の依存関係をすべてモックに置き換える(自クラス以外は全部モック)
  • ユニットテストの単位はクラス(1度に1つのクラスだけチェック)
  • コードを検証する
  • 不変的な(EnumやConst)依存関係以外はモックをする
  • 単一クラスで完結しないクラスは結合テストとして扱う

2.1.1. ロンドン派の例

私が犬を呼ぶと、犬はまず左前足を動かし、次に右前足を動かし、頭を動かし、尻尾を動かす。
そして足を動かし、頭を回転させ、尻尾を振って…

引用:Unit Testing Principles, Practices, and Patterns: ...

2.1.2. メリット

  • 粒度が細かい
    • テストが細かく、1度に1つのクラスだけをチェックするので隅から隅までテストになる
  • 依存関係が複雑な場合でも容易にテストできる
    • モックに置き換えるだけで良いのでテストが簡単に書ける
  • テスト失敗時にどの機能がバグを出しているかを簡単に見つけられる
    • 単一クラスのみテストを書くので、失敗時どのクラスが悪影響を与えているかがわかる

2.1.3. ロンドン派の代表書籍

2.2. デトロイト派(古典派)

  • テスト対象の依存関係は共有依存関係だけモックにする
    • 共有依存関係とは、ユニットテスト間で共有されるもの(データベースなど)
  • ユニットテストの単位は1つの振る舞い(同時に複数クラスがまたがるテストもある)
  • 動作(振る舞い)を検証する
  • 他のテストとは切り離して実行する(テスト同士は違いに影響しない)
  • 2つ以上の動作単位を検証する場合は統合テストとして扱う

2.2.1. デトロイト派の例

私が犬を呼ぶと、犬はすぐに私のところに来る。

引用:Unit Testing Principles, Practices, and Patterns: Effective testing styles, patterns, and reliable automation for unit testing, mocking, and integration testing with examples in C#

2.2.2. メリット

  • 依存関係が複雑な場合のテストは難しい
    • 難しいということは設計を見直す必要があり、良い設計に繋がる
  • 1つの動作を担保できる
    • 統合時にバグが起きる危険性を排除できる
  • テストコードがドキュメントになる
    • 動作単位を書くと言うことは、アプリがどのような振る舞いを持つのかわかる

2.2.3. デトロイト派の代表書籍

3. TDDと流派

みんな大好きテスト駆動開発(TDD)ですが、ロンドン派かデトロイト派かでアプローチの方法が変わります。

ロンドンスタイルのTDDはアウトサイドイン型のアプローチを取ります。
関係するクラスはすべてモックとして実装して処理は後回しにし、1つのクラスの実装に注力します。

デトロイトスタイルのTDDはインサイドアウト型のアプローチを取ります。
ドメインモデルから実装を始め、どんどんレイヤーを重ねていきます。

重要な違いはテストと実装の結合度です。

ロンドンスタイルでは、実装と結合するテストを作成する傾向にあるのでテストの保守性が大きく下がってしまいます。

モックを使っているということは、クラスAを使用しているクラスBがある時、クラスAの実装を変えたとしてもクラスBはクラスAをモックしている(返却値が一定)ので、クラスAの変更に対するテストができません。

テストも落ちることがなく、正常に実行されたと結果を出すため、発見がしにくく、過去の仕様をテストし続けるテストコードが生き残り続けます。

ロンドン派のアプローチ的にはこの変更は統合テストで検出すべきだと考えていますが、二重でテストすることになるので、変更があった際の修正箇所が増えてしまい、メンテナンスコストが上がってしまいます。

メンテナンスコストが上がってしまうということは、テストが修正されなくなってしまい(テスト削除やテストスキップによる)結果、テストの意味が失われてしまうことに繋がります。

実装を変更してテストが変更されないなんてあるわけないじゃん…と考える人もいますが、すべての人がテストに対するベストプラクティスを知っているわけではないですし、軽視している人もいます。

どこまで不安要素を取り除けるか、そのためにはどうすればいいか、を考えるのが重要になっていきます。

3.1. TDD + デトロイト = BDD

動作を検証するスタイルのデトロイト派とTDDを組み合わせたものがBDDとなります。

BDDは動作を検証することに重きが置かれているので、BDDフレームワークと言われるJestやRSpecには便利なヘルパーメソッドが容易されています。

ittoBeから始まるアサーションメソッドが多いのもその特徴です。

ここで勘違いしてしまいがちなのですが、TDDフレームワーク(xUnit系)でもBDDはできます。

少し手間がかかりますがxUnit系でもBDDはできます。ただの手法なので技術は問わないのです。
ただ自由すぎてしまうので秩序が乱れやすいです。それをある程度矯正するためのBDDフレームワークです。

4. まとめ

  • ユニットテストは流派によって考え方が異なるので、どのようなアプローチを取るか考える必要がある
  • ロンドン派は取っ付きやすく、テストも簡単に書けるが後々テストのメンテナンスコストが高くなり辛い
  • BDDはTDDの拡張で、デトロイト派の考え方に沿った書き方ができる

4.1. 参考

GitHubで編集を提案

Discussion