0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

テスト設計原則についてまとめてみた

Posted at

前置き

以下のテストに関する設計が丁寧に描かれた本を読む中で、改めて基本ではあるものの、
たまに雑にやってしまうことがあったので、再度棚卸のためにも、テスト設計、
テストアーキテクチャに関する内容をまとめ直してみました。

前提

以下には、RDRAという神崎さん考案の要件定義フレームワークが出てきますが、
もうその内容は前提として話を進めます。

境界値分析

概要

エラーが発生しやすい仕様の境界値に焦点を当ててテストケースを作成する技法です。

メカニズム

「正常範囲の最小値」「正常範囲の最大値」「その1つ外側の異常値」をテストデータとして選択します。

例えば、年齢入力が18歳から65歳まで有効な場合、17, 18, 65, 66をテストします。
これにより、「>と≧」のコーディングミスなどを効率的に検出できます。

具体例

送料が5,000円以上の購入で無料になるECサイト。

テストケース

4,999円(境界のすぐ内側)、5,000円(境界値)、5,001円(境界のすぐ外側)の購入金額で、送料が正しく計算されるかを確認する。

要件定義から考えよ

実はこれ、要件定義時の機能要件の段階で顧客と握っておくか、おかないかで、後のテスト工数の大幅な削減につながります。

テストシナリオとして、具体的に「閾値を超えた値の場合には、こういうエラーシナリオを辿ります。」と明文化しましょう。

同値分割

概要

同じように扱われるべき入力値のグループ(同値クラス)から、代表値を1つだけ 選んでテストする技法です。

メカニズム

仕様上、同じ結果になることが期待される入力値の範囲をグループ化し、その中から
一つテストすれば、グループ全体をテストしたと見なします。

これにより、無駄なテストケースを削減します。

具体例

年齢に応じて入場料が変わるシステム(例: 13-18歳は中高生料金)。

同値クラス

・0-12歳(子供料金)→ 代表値として10歳をテスト

10なので、暗算チェックできる

・13-18歳(中高生料金)→ 代表値として15歳をテスト

15だと、ちょうど真ん中で分かりやすい

・19歳以上(大人料金)→ 代表値として30歳をテスト

・負の年齢(無効値)→ 代表値として-5歳をテスト

デシジョンテーブル

概要

複数の条件の組み合わせによって、実行されるべきアクションが決定される複雑なビジネスロジックをテストするための技法です。

要件定義で使うフレームワークのRDRAにも丁度、「バリエーション/条件」という項目で存在しています。

image.png

メカニズム

全ての条件の組み合わせ(真偽)と、それに対応するアクションを表(テーブル)にまとめます。この表の各列が一つのテストケースとなり、仕様の網羅性を高めます。

具体例

ユーザーの会員ランク(ゴールド/通常)とクーポン所持(あり/なし)で割引率が変わるロジック。

条件 ケース1 ケース2 ケース3 ケース4
会員ランク ゴールド ゴールド 通常 通常
クーポン あり なし あり なし
アクション 20%割引 10%割引 10%割引 割引なし

状態遷移テスト

概要

システムの状態が、特定のイベントで起動したユースケースによってどのように変化するかをテストする技法です。

image.png

これもRDRAに定義したステートマシン図と整合させてテストしましょう。

メカニズム

システムの取りうる全ての状態と、状態間を遷移させるイベントを図(状態遷移図)に描きます。

この図の矢印(遷移)を全て通過するようにテストケースを作成することで、予期せぬ状態遷移や不正な操作を検出します。

特にマイクロサービス含む分散アーキテクチャでは、サーガのステートマシン図は必須で描きましょう。

具体例

ECサイトの注文ステータス。

状態

カート → (注文確定イベント) → 決済待ち → (決済完了イベント) → 発送準備中 → (キャンセルイベント) → キャンセル済み

テストケース

「決済待ち」の状態で「キャンセル」ができるか、「発送準備中」の状態で「キャンセル」ができるか、などを網羅的にテストする。

ペアワイズ法

概要

多数のパラメータの組み合わせが存在するテストにおいて、

全てのパラメータのペア(2つの組み合わせ) を少なくとも1回は網羅するようにテストケースを絞り込む技法

です。

メカニズム

全ての組み合わせをテストするとケース数が爆発してしまう問題(組み合わせ爆発)を防ぎます。

多くのバグは、単一のパラメータか、2つのパラメータの相互作用によって発生する

という経験則に基づいています。

3つ以上のパラメータが複雑に絡み合って初めて発生するバグは、比較的稀であるとされています。これは、どのようにテストケースを設計すれば効率的かを教えてくれます。

パレート法則との共通項

ペアワイズ法はパレートの法則の直接的な応用ではありませんが、

「最小限の努力で最大限の効果を得る」

という、同じエフォートレス思考から生まれています。

image.png

両者は似ていますが、適用の仕方が図のように若干異なります。

具体例

Webサイトの互換性テスト。

パラメータ

・ブラウザ:Chrome, Firefox, Safari

・OS:Windows, macOS, Linux

・言語:日本語, 英語

全組み合わせ:3 x 3 x 2 = 18通り

ペアワイズ法

「Chrome & Windows」「Chrome & 日本語」「Windows & 日本語」などのペアを網羅する、より少ないテストケース(6〜8通り程度)に削減できる。

ミューテーションテスト (Mutation Testing)

概要

テストスイートの品質そのものを評価するための、高度なテスト技法です。

メカニズム

プロダクションコードを意図的にわずかに変更(ミューテーション、例: >を≧に変える)し、「ミュータント」と呼ばれるバグのあるコードを生成します。

その後、既存のテストスイートを実行し、そのミュータントを検出(テストを失敗させる)できるかを確認します。

「生き残ったミュータント」は、テストが見逃している観点があることを示唆します。

ただし、いきなりこれを過剰にやることはお勧めしません。
過剰なテスト品質が却って、プロダクトのリリースの阻害要因になりかねません。

具体例

if (age ≧ 20)というコードをif (age > 20)に自動変更し、既存のテストが失敗するかを確認する。
もし失敗しなければ、ageが20ちょうどのケースをテストするテストケースが不足していることがわかります。

テストピラミッド

概要

テストスイート全体における、各テストタイプの理想的な割合を示したモデルです。

image.png

メカニズム

ピラミッドの下層ほど数が多く、実行が速く、分離されたテスト(単体テスト)を配置します。
上層にいくほど数が少なく、実行が遅く、統合されたテスト(E2Eテスト)を配置します。

これにより、フィードバックが速く、失敗箇所の特定が容易なテストスイートを構築します。

ちなみに、この構造はトランザクションスクリプトで実装するのか?
ドメインモデリング式に実装するのか?によっても変わってきます。

トランザクションスクリプト実装

この場合には、どうしても単体テストの粒度がドメイン駆動の場合に比べて大きくなってしまいます。

ファイル名

その結果、上図のようなアイスクリームコーン上になります。

意図的にこのような構造になるのはいいのですが、最悪なのは、

ドメイン駆動で設計と言っているのに、テスト設計がアイスクリームコーンであることです。

これは、時間経過でプロダクションコード自体が、徐々に汚くなる要因を孕んでいます。

最初トランザクションスクリプトで素早く実装し、あとからプロダクトの成長に合わせて、ドメインモデリングに還る場合に関しては、また別の記事で書きます。

ドメインモデリング

よくあるドメインモデリングおよび、イベントソーシングを必要とする、イベント履歴式のドメインモデリングでは、ピラミッド構造なテストであるべきです。

TDDの思想に忠実に行う場合には、先にテストピラミッドの構造に設計を改善した上で、
プロダクションコードの方をそのテストを通過するように実装しましょう。

この場合のテストの設計時にも、

ドメインモデリングやRDRAで描いた成果物は非常に好気的に活用できます。

具体例

・単体テスト (多): 価格計算ロジック、バリデーションルールなど。

・結合テスト (中): 注文サービスがDBに正しく書き込めるか。

・E2Eテスト (少): ユーザーがUIを操作して商品をカートに入れ、決済を完了できるか。

FIRST原則

概要

効果的な単体テストが持つべき特性を示した頭字語です。

詳しい内容は、以下の記事を参照ください。

メカニズム

この原則に従うことで、自然とテストの信頼性と価値が高まります。

具体例

F (Fast)

高速に実行できる(DBの代わりにインメモリDBを使うなど)。

テスト実行プロセス中にボトルネックがあると、そこがデリバリー全体のボトルネックになりかねません。

I (Independent)

他のテストと独立している(テスト間で状態を共有しない)。

これは特に

「テストコンポーネントが正しい単位でまとめられているのか?」

という単位で重要になってきます。

クリーンアーキテクチャ本やちょうぜつ本にある、コンポーネントの原則をテストアーキテクチャにも適用することで、この独立性は非常に満たしやすくなります。

R (Repeatable)

どんな環境でも同じ結果になる(外部APIをモックするなど)。

S (Self-Validating)

自身で成否を検証できる(assert文で結果を検証する)。

T (Timely)

プロダクションコードを書く直前か、並行して書かれる(TDD)。

Arrange-Act-Assert (AAA) パターン

概要

テストメソッドの構造を3つのパートに分けるという、可読性を高めるためのパターンです。

メカニズム

全てのテストが同じ構造を持つことで、一読しただけでそのテストの目的と内容を理解できるようになります。

具体例

image.png

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?