web-dev-qa-db-ja.com

ユニット・テストは、広範囲にモックすることなくどの程度正確に作成する必要がありますか?

私が理解しているように、ユニットテストのポイントは、コードのユニットを分離してテストすることです。この意味は:

  1. それらは無関係なコードコードベースの別の場所で変更することで壊れるべきではありません。
  2. 統合テスト(ヒープで壊れる可能性がある)とは対照的に、テストされるユニットのバグによって壊れるのは1つの単体テストだけです。

これはすべて、テストされたユニットのすべての外部依存関係を模倣する必要があることを意味します。そして、私はすべての外部依存関係を意味します。ネットワーキング、ファイルシステム、データベースなどの「外部レイヤー」だけではありません。

これは論理的な結論につながります。つまり、事実上すべてのユニットテストはモックする必要があります。一方、モックに関するグーグル検索をすばやく検索すると、「モックはコードの匂い」であり、ほとんど(完全ではないが)回避する必要があると主張する記事が数多く表示されます。

さて、質問へ。

  1. 単体テストはどのように適切に記述すべきですか?
  2. それらと統合テストの間の境界はどこにあるのですか?

更新1

次の疑似コードを検討してください。

class Person {
    constructor(calculator) {}

    calculate(a, b) {
        const sum = this.calculator.add(a, b);

        // do some other stuff with the `sum`
    }
}

Calculator依存関係をモックせずにPerson.calculateメソッドをテストするテスト(Calculatorは「外の世界」にアクセスしない軽量クラスであると仮定)は、単体テスト?

82
Alexander Lomia

ユニットテストのポイントは、コードのユニットを個別にテストすることです。

マーティン・ファウラー ユニットテスト

ユニットテストはソフトウェア開発でよく話題になり、プログラムを作成する間ずっと知っていた用語です。しかし、ほとんどのソフトウェア開発用語と同様に、それは非常に不明確であり、実際よりも厳密に定義されていると人々が考えると混乱が生じることがよくあると思います。

ケントベックが書いたものテスト駆動開発、例として

私はそれらを「単体テスト」と呼んでいますが、受け入れられている単体テストの定義とはよく一致していません

「単体テストの要点」という主張は、「単体テスト」のどの定義が考慮されているかに大きく依存します。

プログラムが相互に依存する多くの小さなユニットで構成されているという見方であり、各ユニットを個別にテストするスタイルに制約されている場合、多くの test doubles は避けられません結論。

あなたが見る矛盾するアドバイスは、異なる一連の仮定の下で活動している人々から来ています。

たとえば、リファクタリングのプロセス中に開発者をサポートするテストを記述していて、1つのユニットを2つに分割するがサポートされるべきリファクタリングである場合、何かを与える必要があります。たぶん、この種のテストには別の名前が必要ですか?あるいは、「ユニット」の別の理解が必要かもしれません。

あなたは比較したいかもしれません:

Calculatorの依存関係をモックしないでPerson.calculateメソッドをテストするテスト(Calculatorが「外界」にアクセスしない軽量クラスである場合)は、単体テストと見なすことができますか?

これは間違った質問だと思います。これもlabelsに関する議論ですが、私たちが実際に気にしているのはproperties

コードに変更を加えるときは、テストの分離は気にしません-「間違い」は、検証されていない編集の現在のスタックのどこかにあることはすでに知っています。テストを頻繁に実行する場合、そのスタックの深さを制限し、間違いを見つけるのは簡単です(極端な場合、テストはすべての編集後に実行されます-スタックの最大の深さは1です)。しかし、テストの実行は目標ではありません-それは中断です-したがって、中断の影響を減らすことには価値があります。中断を減らす1つの方法は、テストが高速であることを確認することです( Gary Bernhardtは300msを提案しています ですが、自分の環境でそれを行う方法はわかりません)。

Calculator::addを呼び出してもテストの実行に必要な時間が大幅に増加しない場合(またはこのユースケースのその他の重要なプロパティのいずれか)、テストダブルを使用する必要はありません-提供しませんコストを上回るメリット。

ここで2つの仮定に注意してください。コスト評価の一部としての人間と、利益評価における検証されていない変更の短いスタックです。これらの条件が満たされない状況では、「分離」の値はかなり変化します。

Harry Percivalによる Hot Lava も参照してください。

59
VoiceOfUnreason

ユニット・テストは、広範囲にモックすることなくどの程度正確に作成する必要がありますか?

コードの副作用を最小限に抑える。

たとえば、calculatorがWeb APIと通信する場合のサンプルコードを使用して、そのWeb APIとの対話に依存する脆弱なテストを作成するか、そのモックを作成します。ただし、確定的で状態のない計算関数のセットである場合は、モックしないでください(モックしないでください)。その場合、モックが実際のコードとは異なる動作をするリスクがあり、テストでバグが発生します。

モックは、ファイルシステム、データベース、URLエンドポイントなどに読み書きするコードにのみ必要です。あなたが実行している環境に依存している;または、本質的に非常にステートフルで非決定的です。したがって、コードのこれらの部分を最小限に抑え、抽象化の背後に隠すと、それらは簡単にモック化され、残りのコードはモックの必要性を回避します。

副作用のあるコードポイントについては、モックするテストとモックしないテストを書く価値があります。後者は本質的に壊れやすく、場合によっては遅くなるため、注意が必要です。したがって、コードを保存してビルドするたびにではなく、CIサーバーで一晩だけ実行したい場合があります。ただし、前者のテストは、可能な限り頻繁に実行する必要があります。各テストが単体テストであるか統合テストであるかについては、アカデミックになり、単体テストであるものとそうでないものとの「炎の戦争」を回避します。

40
David Arno

これらの質問の難しさはかなり異なります。最初に質問2を見てみましょう。

単体テストと統合テストは明確に分離されています。ユニットテストはoneユニット(メソッドまたはクラス)をテストし、その目標を達成するために必要なだけ他のユニットを使用します。あざける必要があるかもしれませんが、それはテストのポイントではありません。統合テストは、異なるactualユニット間の相互作用をテストします。この違いが、私たちがneed単体テストと統合テストの両方を行った理由の全体です。一方が他方の仕事を十分に果たした場合、私たちはそうしませんが、通常、2つを使用する方がより効率的であることが判明しました1つの汎用ツールではなく、専用ツール。

さて、重要な質問について:どのようにユニットテストを行うべきですか上記のように、単体テストは補助構造必要な場合のみを構築する必要があります。多くの場合、実際のデータベースやany実際のデータベースよりもモックデータベースを使用する方が簡単です。ただし、モック自体は価値がありません。頻繁に発生する場合は、実際には、中間レベルの単体テストの入力として別のレイヤーのactualコンポーネントを使用する方が簡単です。その場合は、遠慮なくご利用ください。

多くの実務家は、ユニットテストBがユニットテストAですでにテストされたクラスを再利用する場合、ユニットAの欠陥が複数の場所でテストの失敗を引き起こすことを恐れています。私はこれを問題ではないと考えています:テストスイートは成功する必要があります100%必要な安心を与えるため、失敗が多すぎることは大きな問題ではありません-結局のところ、あなた- do欠陥があります。唯一の重大な問題は、欠陥があまりにもトリガーした場合です少数失敗。

したがって、あざけるような宗教をしないでください。これは手段であり、目的ではないので、余分な労力をかけずに済むなら、そうすべきです。

30
Kilian Foth

では、質問に直接回答してください。

単体テストはどのように適切に記述すべきですか?

あなたが言うように、依存関係を模倣し、問題のユニットだけをテストする必要があります。

それらと統合テストの境界はどこにあるのでしょうか?

統合テストは、依存関係がモックされていない単体テストです。

電卓をモックしないでPerson.calculateメソッドをテストするテストは、単体テストと見なすことができますか?

いいえ。計算機の依存関係をこのコードに挿入する必要があり、モックバージョンと実際のバージョンのどちらかを選択できます。模擬テストを使用する場合は単体テスト、実際のテストを使用する場合は統合テストです。

ただし、警告。あなたのテストが呼び出されるべきだと人々が何と思うか本当に気にしますか?

しかし、あなたの本当の質問はこれのようです:

あざけることに関するグーグル検索をすばやく検索すると、「あざけることはコードの匂いだ」と主張し、ほとんど(完全にではないが)避けるべきであると主張する記事がたくさん見つかります。

ここでの問題は、多くの人がモックを使用して依存関係を完全に再現することだと思います。たとえば、私はあなたの例の電卓を次のように模擬するかもしれません

public class MockCalc : ICalculator
{
     public Add(int a, int b) { return 4; }
}

私は次のようなことはしません:

myMock = Mock<ICalculator>().Add((a,b) => {return a + b;})
myPerson.Calculate()
Assert.WasCalled(myMock.Add());

それは「私のモックをテストすること」または「実装をテストすること」であろうと私は主張します。私は「モックを書かないでください! *そのように」と言います。

他の人々は私に反対します、私たちはモックへの最善の方法について私たちのブログで大規模な炎の戦争を開始します。良いテストを書きたいだけの人に。

7
Ewan
  1. 単体テストを適切に実装するにはどうすればよいですか?

私の経験則は、適切な単体テストです:

  • 実装ではなくインターフェイスに対してコーディングされています。これには多くの利点があります。 1つは、クラスが [〜#〜] solid [〜#〜] から 依存関係逆転原理 に従うことを保証します。また、これは他のクラスが行うこと(right?)なので、テストも同じようにする必要があります。また、これにより、テストコードの多くを再利用しながら、同じインターフェイスの複数の実装をテストできます(初期化と一部のアサーションのみが変更されます)。
  • 自己完結型です。おっしゃったように、外部コードの変更はテスト結果に影響を与えることはできません。そのため、単体テストはビルド時に実行できます。これは、副作用を取り除くためにモックが必要であることを意味します。ただし、依存関係の逆転の原則に従っている場合、これは比較的簡単です。 Spock のような優れたテストフレームワークを使用して、最小限のコーディングでテストで使用する任意のインターフェイスのモック実装を動的に提供できます。つまり、各テストクラスは、1つの実装クラスとテストフレームワーク(およびモデルクラス["beans"])のコードを実行するだけで済みます。
  • 別の実行中のアプリケーションを必要としません。テストが「何かと話す」必要がある場合、それがデータベースであろうとWebサービスであろうと、それは統合テストであり、単体テストではありません。ネットワーク接続またはファイルシステムに線を引きます。たとえば、純粋にインメモリのSQLiteデータベースは、本当に必要な場合の単体テストとしては公平だと私は考えています。

単体テストを複雑にするフレームワークからのユーティリティクラスがある場合、これらの依存関係のモックを容易にするために非常に単純な「ラッパー」インターフェースとクラスを作成すると便利な場合もあります。これらのラッパーは、必ずしも単体テストの対象ではありません。

  1. それら[ユニットテスト]と統合テストの間の境界はどこにあるのですか?

この区別が最も有用であることがわかりました。

  • ユニットテストは「ユーザーコード」をシミュレートし、実装クラスの動作をコードレベルのインターフェースの望ましい動作とセマンティクスに対して検証します
  • 統合テストはユーザーをシミュレートし、指定されたユースケースおよび/またはフォーマルに対する実行中のアプリケーションの動作を検証しますAPI。 Webサービスの場合、「ユーザー」はクライアントアプリケーションになります。

ここには灰色の領域があります。たとえば、Dockerコンテナーでアプリケーションを実行し、ビルドの最終段階として統合テストを実行し、その後コンテナーを破棄できる場合、それらのテストを「単体テスト」として含めても問題ありませんか?これがあなたの燃える議論であるならば、あなたはかなり良い場所にいます。

  1. 事実上すべての単体テストが模擬する必要があるのは本当ですか?

いいえ。一部の個々のテストケースは、パラメータとしてnullを渡し、例外が発生することを確認するなど、エラー状態を対象としています。そのような多くのテストはモックを必要としません。また、文字列処理や数学関数などの副作用のない実装では、出力を確認するだけなので、モックは必要ありません。しかし、私が思うに、持つ価値のあるほとんどのクラスには、テストコードのどこかに少なくとも1つのモックが必要です。 (少ないほど良いです。)

あなたが言及した「コードのにおい」の問題は、過度に複雑なクラスがあり、テストを書くためにモックの依存関係の長いリストが必要な場合に発生します。これは、実装をリファクタリングして分割する必要がある手がかりであり、各クラスのフットプリントが小さくなり、責任が明確になり、テストが容易になります。これにより、長期的には品質が向上します。

1つの単体テストのみが、テスト済みのユニットのバグによって破損するはずです。

これは再利用に反するため、これは妥当な期待だとは思いません。たとえば、privateメソッドがある場合があります。これは、インターフェースによって公開された複数のpublicメソッドによって呼び出されます。その1つのメソッドに導入されたバグにより、複数のテストが失敗する可能性があります。これは、同じコードを各publicメソッドにコピーする必要があるという意味ではありません。

4
wberry
  1. それらは無関係なコードコードベースの別の場所で変更することで壊れるべきではありません。

このルールがどのように役立つか、私にはよくわかりません。あるクラス/メソッド/何かを変更すると、実稼働コードの別のクラスの動作が壊れる可能性がある場合、実際には、コラボレーターであり、無関係ではありません。テストが壊れて本番コードが壊れない場合は、テストに問題がある可能性があります。

  1. 統合テスト(ヒープで壊れる可能性がある)とは対照的に、テストされるユニットのバグによって壊れるのは1つの単体テストだけです。

このルールも疑わしいと思います。コードを構造化し、1つのバグがユニットテストの失敗を1つだけ引き起こすようにテストを作成するのに十分に優れている場合、コードベースがユースケースに進化しても、すべての潜在的なバグをすでに識別していると言っています。予想していません。

それらと統合テストの境界はどこにあるのでしょうか?

それは重要な違いだとは思いません。とにかくコードの「ユニット」とは何ですか?

コードのそのレベルが処理している問題のドメイン/ビジネスルールに関して、「意味のある」テストを記述できるエントリポイントを見つけてください。多くの場合、これらのテストは本質的にいくらか「機能的」です。入力を入れ、出力が期待どおりであることをテストします。テストがシステムの望ましい動作を表現している場合、製品コードが進化してリファクタリングされても、テストはしばしば非常に安定したままです。

ユニット・テストは、広範囲にモックすることなくどの程度正確に作成する必要がありますか?

Wordの「ユニット」をあまり読みすぎず、実際の本番クラスをテストで使用することに傾倒しないでください。テストにそれらのクラスが複数含まれている場合でも、あまり心配する必要はありません。それらの1つが使いにくい場合(初期化に多くの時間がかかるため、または実際のデータベース/メールサーバーにアクセスする必要があるためなど)、考えをモック化/偽造することになります。

まず、いくつかの定義:

ユニットテストでは、ユニットを他のユニットから分離してテストしますしかし、その意味は信頼できるソースによって具体的に定義されていないため、少しよく定義してみましょう。I/ O境界を超えている場合(I/Oはネットワーク、ディスク、画面、またはUI入力です)、線を引くことができる半客観的な場所があります。コードがI/Oに依存している場合、それはユニット境界を越えているため、そのI/Oを担当するユニットを模擬する必要があります。

その定義の下では、純粋な関数のようなものをあざける理由はないと思います。つまり、単体テストは純粋な関数や副作用のない関数に向いています。

ユニットをエフェクトで単体テストする場合は、エフェクトを担当するユニットを模擬する必要がありますが、代わりに統合テストを検討する必要があります。したがって、簡単に言えば、「モックする必要がある場合は、統合テストが本当に必要かどうかを自問してください」です。しかし、ここにはより良い、より長い答えがあり、ウサギの穴ははるかに深くなります。モックは、それらから学ぶことがたくさんあるので、私のお気に入りのコードの匂いかもしれません。

コードのにおい

これについては、ウィキペディアを参照します。

コンピュータプログラミングでは、コードのにおいは、より深い問題を示している可能性があるプログラムのソースコード内の特性です。

それは後で続きます...

「においは、基本的な設計原則の違反を示し、設計品質に悪影響を及ぼすコード内の特定の構造です。」 Suryanarayana、Girish(2014年11月)。ソフトウェア設計のにおいのためのリファクタリング。モーガン・カウフマン。 p。 258。

コードの匂いは通常バグではありません。それらは技術的に正しくなく、プログラムの機能を妨げるものではありません。代わりに、それらは設計の弱点を示しており、開発を遅らせたり、将来バグや障害のリスクを高めたりする可能性があります。

つまり、すべてのコードの臭いが悪いわけではありません。代わりに、何かがその最適な形式で表現されない可能性があることを示す一般的な兆候であり、匂いmayは問題のコードを改善する機会を示しています。

モックの場合、匂いはモックを要求しているように見えるユニットがモックされるユニットについてdependであることを示します。これは、問題を原子的に解決できる部分に分解していないことを示している可能性があり、thatはソフトウェアの設計上の欠陥を示している可能性があります。

すべてのソフトウェア開発の本質は、大きな問題を小さな独立した部分(分解)に分解し、ソリューションを組み合わせて大きな問題(構成)を解決するアプリケーションを形成するプロセスです。

大きな問題を小さな部分に分解するために使用されるユニットが互いに依存している場合、モッキングが必要です。別の言い方をすると、想定される構成の原子単位が実際に原子的ではなく、分解戦略がより大きな問題をより小さなindependent解決すべき問題に分解できなかった場合、モッキングが必要です。

コードのモックを匂わせるのは、モックに本質的に問題があるということではありません。これをコードのにおいにさせるのは、アプリケーションのカップリングの問題のある原因を示している可能性があるということです。結合のソースを削除する方が、モックを作成するよりもはるかに生産的である場合があります。

カップリングには多くの種類があり、いくつかは他よりも優れています。モックがコードの匂いであることを理解すると、最悪の種類を特定して回避することができますearlyアプリケーション設計のライフサイクルでbefore匂いは何か悪いものに発展します。

2
Eric Elliott

モックは、単体テストであっても最後の手段としてのみ使用してください。

メソッドはユニットではなく、クラスでさえユニットではありません。ユニットとは、それが何と呼ばれるかに関係なく、意味のあるコードの論理的な分離です。十分にテストされたコードを持つことの重要な要素は、自由にリファクタリングできることであり、自由にリファクタリングできることの一部は、そうするためにテストを変更する必要がないことを意味します。モックするほど、リファクタリング時にテストを変更する必要があります。メソッドをユニットと考える場合、リファクタリングするたびにテストを変更する必要があります。また、クラスをユニットと考える場合、クラスを複数のクラスに分割するたびにテストを変更する必要があります。コードをリファクタリングするためにテストをリファクタリングする必要がある場合、人々はコードをリファクタリングしないことを選択します。これは、プロジェクトで発生する可能性がある最悪の事態です。テストをリファクタリングせずにクラスを複数のクラスに分割できることが不可欠です。そうしないと、500行の特大サイズのスパゲッティクラスができてしまいます。ユニットテストでメソッドまたはクラスをユニットとして扱う場合は、オブジェクト指向プログラミングではなく、オブジェクトを使用したある種のミュータント関数プログラミングを行っていると考えられます。

単体テスト用のコードを分離しても、それ以外のすべてを模擬しているわけではありません。もしそうなら、あなたはあなたの言語のMathクラスを模擬する必要があるでしょう、そしてそれが良い考えであると誰も絶対に考えません。内部依存関係は、外部依存関係と同じように扱われるべきではありません。あなたは彼らがよくテストされていて、本来あるべきように機能していると信じています。唯一の本当の違いは、内部の依存関係がモジュールを壊している場合、GitHubに問題を投稿しなくても問題を投稿する必要がなく、理解できないコードベースを調べて修正する必要がないことです。または最高の希望。

コードを分離するということは、内部の依存関係をブラックボックスのように扱い、その中で発生していることをテストしないことを意味します。 1、2、または3の入力を受け入れるモジュールBがあり、それを呼び出すモジュールAがある場合、モジュールAのテストでこれらの各オプションを実行する必要はなく、1つだけ選択してそれを使用します。つまり、モジュールAのテストでは、モジュールBへの応答を処理するさまざまな方法をテストする必要があります。モジュールに渡すものではありません。

したがって、コントローラーが複雑なオブジェクトを依存関係に渡し、その依存関係がいくつかの可能なことを行う場合、おそらくそれをデータベースに保存し、さまざまなエラーを返す可能性がありますが、コントローラーが実際に行うすべてのことは、それが戻るかどうかを確認するだけですエラーの有無にかかわらず、その情報を渡す場合、コントローラーでテストするのは、エラーが返されて渡されるかどうかの1つのテストと、エラーが返されないかどうかの1つのテストです。データベースに何かが保存されたかどうか、またはエラーがどのようなエラーであるかはテストしません。これは統合テストになるためです。これを行うために依存関係を模擬する必要はありません。コードを分離しました。

1
TiggerToo