背景
普段、業務でユニットテストを書いたり、レビューをしたりしています。
ユニットテストがあることは良いことなのですが、困ったことがあります。
それは、ユニットテストのルールが明確じゃない点です。
そのため、人によって、ユニットテストの書き方がマチマチで、なんとかしたいなと困っています。
- 1つのテストケースに、Assertが複数あり、散在している。
- 似たようなテストデータの構築が、いろいろなテストケースにある。
- テストする関数名が、パット見よくわからない。
本記事では、ユニットテストを書く上で守ってほしいことをピックアップしました。
守って欲しいこと
1. 可読性のあるテストコード
1.1. テストメソッド名の統一
テストメソッドの命名規則は、あったほうがよいです。
テストランナーツールの実行結果では、関数名を表示されることがあります。
そこから『どういうテストをしていたのか』が読んで分かると、わざわざテストの中身を見る必要はありません。
下記のような命名規則が良いそうです。
- テスト対象のメソッドの名前。
- それがテストされるシナリオ。
- シナリオが呼び出されたときに想定される動作。
Test_Single
Add_SingleNumber_ReturnsSameNumber
※テストの名前付けより引用
※ 『正しく表示されていること』という期待値は、極力避けましょう。具体的な値を期待値に設定しましょう。
1.2. 論理制御を避ける
ifやfor等の制御は、テストコードの見通しが悪くなるため、極力避けましょう。
共通化するよりも、愚直に書いたほうが読みやすいです。パフォーマンスは、そこまで気にしなくて良いですから。
1.3. マジックナンバーを避ける
プロダクトコードでは、ちゃんとマジックナンバーを避けていても、
テストコードでは、マジックナンバーを使っていることがあります。
横着せずに、ちゃんと適切な変数名で表現しましょう。
1.4. テストデータを外部ファイルに切り出す
テストコードを書いていると、テストに必要なテストデータの構築をする必要があります。
テストケース毎に書いても良いですが、見通しを良くするためにテストデータを別ファイルに分けましょう。
ただ、2つ以上のテストケースで参照される場合のみにしておきましょう。
テストデータは、プログラミング言語の相性に良い構造化ファイルを使うと良いです。
- JSON
- CSV
- XML
- YAML
1.5. テストケースの構成を統一する
テストケースは、3つの構成で記述すると読みやすいです。
- Arrange (準備)
- Act (実行)
- Assert (検証)
これらを上から順番に実行されるようにテストコードを書いていきましょう。
Arrange => Act => Arrange => Act => Assert
のように順番をMixするよりも、
Arrange => Arrange => Act => => Act => Assert
のように順番を揃えるほうが読みやすいです。
1.6. 1つのテストケースで複数の検証をしない
1つのテストケースに、複数の検証を行ってしまうと、何のテストケースなのかわかりにくくなってしまいます。そのため、検証するのは1つに絞るようにしましょう。
例外としては、コンストラクタの検証です。インスタンス化したオブジェクトの属性(プロパティ)値を検証することがあるので、その際は、複数の検証を許可します。
1.7. テストコードは最小限に
テスト対象のインスタンスに、検証しない・不必要なデータの設定することは、余計な混乱を招くため、やめましょう。
また、設定するデータは、hogeなどの意味のない値を設定するのではなく、本物に近いデータを設定しましょう。
2. Matcherの活用
2.1. 自然言語に近い形式でAssertする
検証すべき変数をBooleanの形に変換し、AssertTrueすることが多々あります。
しかし、読みやすさの観点でいうと、Matcherに標準で備わっている関数を使う方が読みやすいです。
assertTrue(actual.contains('hello'))
assertThat(actual, hasItems('hello'))
-> assert that actual has items "hello".
2.2. 変数名をわかりやすくする
テストコードを書くときは、期待する値をExpect, 実際の値をActualという
変数名にしましょう。そうすると、何を検証しているのかわかりやすくなります。
また、モックやスタブ、スパイと言ったテストコードでよく出てくる用語を変数名で使用する際、きちんと使い分けをしましょう。モックという変数名なのに、実際はスタブのような使い方をしていると、誤解を招いて困ります。
3. テストパターン
3.1. ホワイトボックステストとブラックボックステスト
テストコードを書いている人は、基本的には内部仕様を把握しているはずなので、ホワイトボックスでテストを書くことが多いと思います。
また、網羅性を高めるために、パラメータテスト(組み合わせテスト)や、同値、境界値のようなブラックボックステストも書くのも良いでしょう。
3.2. 正常・準正常・異常系
種類 | 内容 |
---|---|
正常系テスト | 標準的な振る舞いをテスト |
準正常系テスト | バリデーションエラー等のエラーに対するテスト |
異常系テスト | API接続エラー等のエラーに対するテスト |
3.3. Failのテスト
テストコードに、予期しない処理が走った場合、強制的にFailにさせることも大切です。
例えば、『非推奨な機能が使用された場合、Failにさせる』は便利です。
3.4. privateメソッドの検証
privateメソッドの検証は、publicメソッド経由で検証しましょう。
4. 大量にあるテストコード
4.1. プロダクトコードと同じ階層構造
プロダクトコードと同様に、テストコードもフォルダ毎にグループピングすると良いでしょう。
横並びにテストコードがあると、読みにくくなるからです。
例えば、プロダクトコードと全く同じ階層構造のフォルダ構成にすると良いかも知れません。
4.2. 単位を分けたテストコード
次のような単位でグルーピングすると良いです。
- 初期化処理を含めた単位でグループ化
- ex. 在庫している商品が"空"の場合、 "1つ"だけの場合、 "複数"の場合
よくあるテストクラスを継承してテストクラスを作るケースがありますが、避けたほうがよいでしょう。いつの間にか、必要以上の巨大な機能が備わってしまっています。SetupやTeardownに、必要な処理をセットしましょう。
4.3. テスト実行時間が長くなってしまったら
下記のいずれかを試すと良いでしょう。
- テストの実行環境を強化する(マシンスペック)
- 並列にする
- 実行するテストを絞り込む
そもそものプロダクトコードのパフォーマンスが悪いのであれば、そちらを改善するのが一番良いでしょう。
終わりに
書いていて、感じたことは『テストコードは、読みやすさが大事』という点です。
プロダクトコードの書き方と、テストコードの書き方は、似ているようで違います。
プロダクトコードでは、保守性の高い洗練されたクラス設計や、ハイパフォーマンスが発揮される処理が求められることがあります。しかし、テストコードはそれらを求められていないと思います。
テストコードには、プロダクトコードの仕様を素直に表現されたドキュメントとして扱われるべきです。
意図が伝わりづらいテストコードがあった場合、『このテストケースが落ちているけど、なんで?』と疑問を持ち、『落ちているのが正しいの?』と疑心暗鬼に陥ってしまいます。それはとても残念です。そんなことにならないためにも、テストコードは、『読みやすさ』を大切にして設計すべきと思います。