「良い」単体テストを書く方法は?


61

このスレッドをきっかけに、私は(再び)プロジェクトでユニットテストを最終的に使用することを考えています。そこのいくつかのポスターは、「テストは良いテストだとすれば、テストはクールだ」と言っています。私の今の質問:「良い」テストとは何ですか?

私のアプリケーションでは、主な部分は多くの場合、大量の観測データに応じた何らかの数値解析であり、このデータをモデル化するために使用できるフィット関数になります。可能な入力と結果の数が多すぎてすべてのケースをテストすることができないため、これらのメソッドのテストを構築することは特に困難であることがわかりました。この種の方法の「良い」テストに特に興味があります。


8
優れた単体テストでは、1つのことだけをテストする必要があります。失敗した場合は、何が間違っていたかを正確に知る必要があります。
ギャブリン

2
大量のデータがある場合、データファイルを入力として使用できる一般的なテストを作成することをお勧めします。通常、データファイルには入力と期待される結果の両方が含まれている必要があります。xunitテストフレームワークを使用すると、テストケースをその場で(データサンプルごとに1つ)生成できます。
froderik

2
@gablin「失敗した場合、何が間違っていたかを正確に知る必要があります」とは、テストの出力から原因を特定できる限り、複数の考えられる失敗の原因があるテストで問題ないことを示唆します...?
user253751

単体テストでは、操作にかかる時間をテストできるとは誰も言及していないようです。パフォーマンスを念頭に置いてコードをリファクタリングし、ユニットテストで時間と結果に基づいてコードが成功するか失敗するかを確認できます。
CJデニス

回答:


52

単体テストのアートには、単体テストについて次のように述べられています。

単体テストには次のプロパティが必要です。

  • それは自動化され、再現可能である必要があります。
  • 簡単に実装できるはずです。
  • 作成したら、将来の使用に備えて残しておく必要があります。
  • 誰でも実行できるはずです。
  • ボタンを押すだけで実行されるはずです。
  • すぐに実行されるはずです。

その後、完全に自動化され、信頼でき、読みやすく、保守可能である必要があることを追加します。

まだ読んでいない場合は、この本を読むことを強くお勧めします。

私の意見では、これらはすべて非常に重要ですが、最後の3つ(信頼性、読み取り可能性、保守可能性)は特に、テストにこれらの3つのプロパティがある場合、コードにも通常同様に含まれます。


1
ユニットテスト(統合テストまたは機能テストではない)を対象とした包括的なリストの+1
ゲイリーロウ

1
リンクの+1。興味深い資料があります。
ジョリスメイズ

1
「迅速に実行」には大きな意味があります。データベース、ファイルシステム、Webサービスなどの外部リソースから離れて、単体テストを単独で実行する必要がある理由の1つです。これが、モック/スタブにつながります。
マイケルイースター14

1
それが言うとき、それはIt should run at the push of a buttonユニットテストがコンテナ(アプリサーバー)の実行(テスト対象のユニット)またはリソース接続(DB、外部Webサービスなど)を必要としないことを意味しますか?アプリケーションのどの部分を単体テストするべきか、どの部分をテストすべきでないかについて、私はただ混乱しています。ユニットテストはDB接続と実行中のコンテナに依存する必要はなく、代わりにモックアップを使用する必要があると言われました。
両生類16

42

優れた単体テストは、テストしている機能を反映していません。

非常に単純化された例として、2つのintの平均を返す関数があるとします。最も包括的なテストでは、関数を呼び出して、結果が実際に平均であるかどうかを確認します。これはまったく意味がありません。テストしている機能をミラーリング(複製)しています。メイン関数でミスをした場合、テストでも同じミスをします。

つまり、単体テストの主な機能を複製していることに気付いた場合、時間を無駄にしている可能性が高いと言えます。


21
+1この場合に行うことは、ハードコーディングされた引数でテストし、既知の答えと照合することです。
マイケルK

その匂いを見たことがあります。
ポール・ブッチャー

平均を返す関数の適切な単体テストの例を教えてください。
VLAS

2
@VLASは定義済みの値をテストします。たとえば、avg(1、3)== 2を確認します。さらに重要なこととして、INT_MAX、ゼロ、負の値などのエッジケースもチェックします。このバグが再導入されないことを確認するためのテスト。
mojuba

面白い。これらのテスト入力に対して正しい答えを取得し、潜在的にテスト対象のコードと同じ間違いをしないようにすることをどのように提案しますか?
ティモ

10

優れた単体テストは、基本的に実行可能な形式の仕様です。

  1. ユースケースに対応するコードの動作を説明する
  2. 技術的なコーナーケースをカバーします(nullが渡された場合に発生すること)-コーナーケースのテストが存在しない場合、動作は未定義です。
  3. テストされたコードが仕様から変更された場合に中断する

基本的にAPIを最初に作成し、実際の実装を作成するため、テスト駆動開発がライブラリルーチンに非常に適していることがわかりました。


7

TDDの場合、「良い」テストは、顧客が望む機能をテストします。機能は必ずしも機能に対応するとは限らず、開発者がテストシナリオを作成する必要はありません

あなたの場合-私は推測しています-「機能」は、フィット関数が特定の許容誤差内で入力データをモデル化することです。あなたが本当に何をしているのかわからないので、私は何かを作り上げています。うまくいけば、それは痛ましいです。

サンプルストーリー:

[X-Wing Pilot]として、[標的のコンピューターがボックスキャニオンをフルスピードで移動するときにデススターの排気口に到達できるように] [0.0001%未満の適合エラー]が必要です。

そのため、パイロット(および感覚がある場合はターゲットコンピューター)と話をします。最初に「正常」とは何かについて話し、次に異常について話します。このシナリオで本当に重要なこと、一般的なこと、起こりそうにないこと、および単に可能なことを見つけます。

通常、7チャネルのテレメトリデータ(速度、ピッチ、ロール、ヨー、ターゲットベクトル、ターゲットサイズ、ターゲット速度)に0.5秒のウィンドウがあり、これらの値は一定であるか、線形に変化するとします。異常にチャンネルが少なくなったり、値が急速に変化する可能性があります。そのため、一緒に次のようなテストを考え出します。

//Scenario 1 - can you hit the side of a barn?
Given:
    all 7 channels with no dropouts for the full half-second window,
When:
    speed is zero
    and target velocity is zero
    and all other values are constant,
Then:
    the error coefficient must be zero

//Scenario 2 - can you hit a turtle?
Given:
    all 7 channels with no dropouts for the full half-second window,
When:
    speed is zero
    and target velocity is less than c
    and all other values are constant,
Then:
    the error coefficient must be less than 0.0000000001/ns

...

//Scenario 42 - death blossom
Given:
    all 7 channels with 30% dropout and a 0.05 second sampling window
When:
    speed is zero
    and position is within enemy cluster
    and all targets are stationary
Then:
    the error coefficient must be less than 0.000001/ns for each target

ストーリーで説明されている特定の状況にはシナリオがないことに気づいたかもしれません。顧客や他の利害関係者と話をした後、元のストーリーのその目標は単なる仮想的な例でした。実際のテストは、その後の議論から生まれました。これは起こる可能性があります。ストーリーは書き直す必要がありますが、[ストーリーは単なる顧客との会話のプレースホルダーであるため]である必要はありません。


5

最小数の入力(1または0の可能性あり)といくつかの標準ケースのみを含むテストセットのような、コーナーケースのテストを作成します。これらの単体テストは、完全な受け入れテストに代わるものではありません。


5

頻繁に入力されるコードのテストを作成せず、あまり入力されないコードのテストを作成するために多大な労力を費やしている多くのケースを見てきました。

座ってテストを書く前に、何らかのコールグラフを見て、適切なカバレッジを計画していることを確認する必要があります。

さらに、「ええ、私たちはそれをテストします」と言うためだけにテストを書くことを信じていません。ドロップインされて不変のままであるライブラリを使用している場合、変更しないAPIの内部が期待どおりに動作することを確認するテストを書くのに1日も無駄にしない呼び出しグラフの上位。このライブラリを使用するテスト(独自のコード)がこれを指摘しています。


しかし、後日、ライブラリにバグ修正付きの新しいバージョンが含まれる場合はどうなりますか?

@ThorbjørnRavn Andersen-それは、ライブラリ、変更点、および独自のテストプロセスに依存します。コードを所定の位置にドロップしたときに動作することがわかっているコードのテストは作成しません。したがって、更新後に機能する場合は、心の外に行く:)もちろん例外があります。
ティムポスト

あなたはあなたのライブラリーに依存している場合、あなたが行うことができます少なくともあなたが言った期待するかを示すテストを書くことであるライブラリ実際にするために行う

...そして、それが変更された場合、そのライブラリを消費するものをテストします... tl; dr; サードパーティのコードの内部をテストする必要はありません。ただし、わかりやすくするために回答を更新しました。
ティムポスト

4

それほどTDDではありませんが、QAに進んだ後、QAプロセス中に発生するバグを再現するテストケースを設定することで、テストを改善できます。これは、長期間のサポートを利用していて、古いバグをうっかり再導入する危険性がある場所に着手する場合に特に役立ちます。キャプチャするためのテストを実施することは、特に価値があります。


3

すべてのテストで1つのことだけをテストするようにします。各テストにshouldDoSomething()のような名前を付けようとします。実装ではなく、動作をテストしようとしています。パブリックメソッドのみをテストします。

私は通常、成功のための1つまたはいくつかのテストがあり、その後、パブリックメソッドごとに、失敗のためのテストがいくつかあります。

モックアップをよく使います。PowerMockなど、優れたモックフレームワークはおそらく非常に役立ちます。まだ何も使っていませんが。

クラスAが別のクラスBを使用する場合、インターフェイスXを追加して、AがBを直接使用しないようにします。次に、モックアップXMockupを作成し、テストでBの代わりに使用します。これは、テストの実行を高速化し、テストの複雑さを軽減し、Bの特性に対処する必要がないため、Aに書き込むテストの数も減らします。たとえば、AがX.someMethod()を呼び出すことをテストできますB.someMethod()を呼び出す副作用の代わりに。

テストコードもきれいにしてください。

データベースレイヤーなどのAPIを使用する場合は、モックを作成し、コマンドのあらゆる機会にモックアップが例外をスローできるようにします。その後、テストをスローせずに実行し、ループが発生するたびに、次の機会にテストが成功するまで例外をスローします。Symbianで利用可能なメモリテストに少し似ています。


2

Andry LowryがRoy Osheroveの単体テストのメトリックを既に投稿しているようです。しかし、ボブおじさんがクリーンコード(132-133)で提供する(無料の)セットを誰も提示していないようです。彼は頭字語FIRSTを使用します(ここでは要約を示します)。

  • 高速高速で実行する必要があるため、実行してもかまいません)
  • 独立(テストは互いにセットアップまたはティアダウンを行うべきではありません)
  • 繰り返し可能(すべての環境/プラットフォームで実行する必要があります)
  • 自己検証(完全に自動化されています。出力はログファイルではなく「合格」または「不合格」である必要があります)
  • タイムリー(それらを記述するタイミング-テストする本番コードを記述する直前)
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.