ユニットテストフレームワークは本当に必要ですか?


19

現在、私の仕事では、C ++アプリケーション用の単体テストの大規模なスイートを用意しています。ただし、単体テストフレームワークは使用しません。それらは、基本的にassertとcoutをラップするCマクロを単に利用します。何かのようなもの:

VERIFY(cond) if (!(cond)) {std::cout << "unit test failed at " << __FILE__ << "," << __LINE__; asserst(false)}

次に、次のような各テストの関数を作成します。

void CheckBehaviorYWhenXHappens()
{
    // a bunch of code to run the test
    //
    VERIFY(blah != blah2);
    // more VERIFY's as needed
}

CIサーバーは「ユニットテストに失敗しました」を選択し、ビルドに失敗し、開発者にメッセージを送信します。

また、セットアップコードが重複している場合は、本番環境にある他の重複コードと同じようにリファクタリングします。ヘルパー関数の背後にラップし、いくつかのテストクラスをラップして、頻繁に使用されるシナリオを設定します。

CppUnitやboostユニットテストのようなフレームワークがあることは知っています。これらはどのような価値をもたらすのだろうか?これらがテーブルにもたらすものを見逃していますか?それらから得ることができる何か有用なものはありますか?私たちが持っているものは非常にシンプルでうまく機能しているように見えるので、特に本当の価値を追加しない限り、依存関係を追加することをmしています。

回答:


8

他の人がすでに言ったように、あなたはすでにあなた自身の、シンプルな、自家製のフレームワークを持っています。

作成するのは簡単なようです。ただし、ユニットテストフレームワークには、言語の高度な知識を必要とするため、実装がそれほど容易ではない機能がいくつかあります。私がテストフレームワークから通常必要とし、自作するのはそれほど簡単ではない機能は次のとおりです。

  • テストケースの自動収集。つまり、新しいテストメソッドを定義するだけで、それを実行できます。JUnitは、名前がで始まるすべてのメソッドを自動的に収集しtestます[Test]。NUnitには注釈があり、Boost.TestはBOOST_AUTO_TEST_CASEand BOOST_FIXTURE_TEST_CASEマクロを使用します。

    それはほとんど利便性ですが、あなたが得ることができるあらゆる利便性は、開発者が実際に彼らがすべきテストを書いて、彼らが正しくそれらを接続する機会を改善します。長い指示がある場合、誰かがそれらの一部を見逃してしまい、おそらくいくつかのテストが実行されず、誰も気付かないでしょう。

  • コードを調整したり再コンパイルしたりすることなく、選択したテストケースを実行する機能。適切な単体テストフレームワークでは、コマンドラインで実行するテストを指定できます。単体テストでデバッグする場合(多くの開発者にとって最も重要なポイント)、実行するコードの一部だけを選択できる必要があります。コードをあちこち調整する必要はありません。

    バグレポート#4211を受け取ったばかりで、ユニットテストで再現できるとします。したがって、1つを記述しますが、そのテストだけを実行するようにランナーに指示する必要があるため、そこで実際に間違っていることをデバッグできます。

  • チェック自体を変更せずに、テストケースごとにテストで予想される失敗をマークする機能。実際にこれを取得するために、作業中のフレームワークを切り替えました。

    適切なサイズのテストスイートにはテストがありますが、テストする機能がまだ実装されていないか、まだ完了していないか、まだ修正する時間がなかったために失敗しています。テストを予想される失敗としてマークする機能がない場合、定期的にいくつかのエラーが発生しても別の失敗に気付かないため、テストは主な目的の提供を停止します。


おかげでこれが最良の答えだと思います。現在、私のマクロはその仕事をしていますが、あなたが言及した機能のいずれも実行できません。
ダグT.

1
@Jan Hudec「ほとんどの場合便利ですが、得ることができるすべての便利さにより、開発者が実際にすべきテストを作成し、それらを正しく接続できる可能性が向上します。」; すべてのテストフレームワークは、(1)インストールするのは簡単であり、多くの場合、最新の有効な指示よりも古いまたは完全でないインストール指示があります。(2)中間のインターフェイスなしでテストフレームワークに直接コミットする場合、あなたはそれと結婚しているので、フレームワークの切り替えは必ずしも簡単ではありません。
ドミトリー

@Jan Hudecユニットテストを書く人が増えると予想される場合、Googleで「ユニットテストとは」よりも「ユニットテストとは」の結果が多くなければなりません。単体テストが単体テストフレームワークや単体テストの定義から独立していることがわからない場合、単体テストを行う意味はありません。ユニットテストとは何かを十分に理解していない限り、ユニットテストを行うことはできません。そうでなければ、ユニットテストを行う意味がありません。
ドミトリー

私はこの便利な議論を買いません。サンプルのささいな世界を離れると、テストコードの記述は非常に困難になります。このモックアップ、セットアップ、ライブラリ、外部モックアップサーバープログラムなどはすべて、テストフレームワークを完全に把握している必要があります。
ローター

@Lothar、はい、それはすべて多くの仕事であり、学ぶことはたくさんありますが、いくつかの有用なユーティリティが不足しているため、単純な定型文を何度も何度も書く必要があるため、作業があまり楽しくなく、有効性に顕著な違いが生じます。
Jan Hudec

27

あなたはすでにフレームワーク、自家製のものを使用しているようです。

より一般的なフレームワークの付加価値は何ですか?彼らが追加する価値は、広く知られており広く使用されているフレームワークに基づいているため、社外の人とコードを交換しなければならないときにできるということです。

一方、自家製のフレームワークでは、コードを決して共有しないか、フレームワーク自体を提供する必要があり、フレームワーク自体の成長に伴い面倒になる場合があります。

同僚に、説明も単体テストフレームワークもなしにコードをそのまま渡すと、同僚はそれをコンパイルできません。

自家製のフレームワークの2番目の欠点は、互換性です。人気のある単体テストフレームワークは、さまざまなIDE、バージョン管理システムなどとの互換性を保証する傾向があります。新しいIDEまたは新しいVCSに?車輪を再発明しますか?

最後になりましたが、大規模なフレームワークは、いつか独自のフレームワークに実装する必要がある機能を提供します。Assert.AreEqual(expected, actual)必ずしも十分ではありません。必要な場合:

  • 精度を測定しますか?

    Assert.AreEqual(3.1415926535897932384626433832795, actual, 25)
    
  • 実行時間が長すぎる場合の無効テスト?非同期プログラミングを容易にする言語でも、タイムアウトの再実装は簡単ではない場合があります。

  • 例外がスローされることを期待するメソッドをテストしますか?

  • よりエレガントなコードがありますか?

    Assert.Verify(a == null);
    

    は問題ありませんが、代わりに次の行を書く意図をより表現していませんか?

    Assert.IsNull(a);
    

使用する「フレームワーク」はすべて非常に小さなヘッダーファイルにあり、assertのセマンティクスに従います。だから私はあなたがリストする欠点についてあまり心配していません。
ダグT.

4
アサートはテストフレームワークの最も些細な部分だと思います。テストケースを収集して実行し、結果をチェックするランナーは、重要な部分です。
ジャン・ヒューデック

@Janあまりフォローしていません。私のランナーは、すべてのC ++プログラムに共通するメインルーチンです。単体テストフレームワークランナーは、より洗練された有用なことを行いますか?
ダグT.

1
フレームワークでは、メインメソッドでアサートと実行中のテストのセマンティクスのみを許可しています...これまでのところ。あなたがグループにあなたの複数のシナリオ、一緒に初期化されたデータなどに基づいて、グループ関連のシナリオにアサート持つまで待つだけ
ジェームズKingsbery

@DougT .:はい、まともな単体テストフレームワークランナーは、より洗練された便利なことを行います。私の完全な答えをご覧ください。
ジャン・ヒューデック

4

他の人がすでに言ったように、あなたはすでにあなた自身の、自家製のフレームワークを持っています。

他のテストフレームワークを使用することでわかる唯一の理由は、業界の「常識」の観点からです。新しい開発者は、自分で作った方法を学ぶ必要はありません(ただし、非常にシンプルに見えます)。

また、他のテストフレームワークには、利用できる機能がさらにある場合があります。


1
同意した。現在のテスト戦略で制限に直面していない場合、変更する理由はほとんどありません。優れたフレームワークは、おそらくより優れた編成およびレポート機能を提供しますが、コードベース(ビルドシステムを含む)との統合に必要な追加作業を正当化する必要があります。
TMN

3

単純なフレームワークであっても、すでにフレームワークを持っています。

私が見ているより大きなフレームワークの主な利点は、さまざまな種類のアサーション(レイズのアサートなど)を持つ能力、ユニットテストの論理的な順序、およびユニットテストのサブセットのみを実行できることです。時間。また、xUnitテストのパターンは、可能であれば従うのがかなり良いです。たとえば、setUP()やtearDown()などです。もちろん、それはあなたを前述のフレームワークに閉じ込めます。一部のフレームワークは、他のフレームワークよりも優れたモック統合を持っていることに注意してください。たとえば、googleモックとテストです。

すべてのユニットテストを新しいフレームワークにリファクタリングするのにどれくらい時間がかかりますか?数日または数週間はそれだけの価値があるかもしれませんが、多分そうではないでしょう。


2

私の見方では、あなたには両方の利点があり、あなたは「欠点」(原文のまま)にいます。

利点は、快適で、自分に合ったシステムを用意していることです。製品の妥当性を確認できてうれしいです。おそらく、異なるフレームワークを使用するもののすべてのテストを変更しようとしても、ビジネス上の価値はありません。コードをリファクタリングでき、テストが変更を取得する場合、またはさらに良いことには、テストを変更でき、リファクタリングされるまで既存のコードがテストに失敗する場合、すべてのベースがカバーされます。しかしながら...

ユニットテストAPIが適切に設計されていることの利点の1つは、ほとんどの最新のIDEで多くのネイティブサポートがあることです。これはハードコアVIには影響せず、Visual Studioユーザーをあざ笑うemacsユーザーもいますが、優れたIDEを使用するユーザーの場合は、テストをデバッグして実行することができます。 IDE自体。これは良いことですが、使用するフレームワークによってはさらに大きな利点があり、それはコードのテストに使用される言語にあります。

languageと言うとき、私はプログラミング言語について話しているのではなく、テストコードを物語のように読みやすくする流fluentな構文に包まれた豊富なセットワードについて話しているのです。特に、私はBDDフレームワークの使用を支持しています。私のお気に入りのDotNet BDD APIはStoryQです、しかし、同じ基本的な目的を持つ他のいくつかがあります。これは、要件ドキュメントから概念を取り出し、仕様で記述されている方法と同様の方法でコードで記述することです。しかし、本当に優れたAPIは、テスト内の個々のステートメントをすべてインターセプトし、そのステートメントが正常に実行されたか失敗したかを示すことにより、さらに進化します。これは非常に便利です。早期に戻ることなく実行されたテスト全体を確認できるため、呼び出し全体をデコードすることなく、失敗したテストの部分に注意を集中するだけでデバッグ作業が非常に効率的になります。シーケンス。もう1つの良い点は、テスト出力にこの情報がすべて表示されることです。

私が話していることの例として、以下を比較してください。

アサートの使用:

Assert(variable_A == expected_value_1); // if this fails...
Assert(variable_B == expected_value_2); // ...this will not execute
Assert(variable_C == expected_value_3); // ...and nor will this!

流fluentなBDD APIの使用:( イタリック体のビットが基本的にメソッドポインターであることを想像してください)

WithScenario("Test Scenario")
    .Given(*AConfiguration*) // each method
    .When(*MyMethodToTestIsCalledWith*, variable_A, variable_B, variable_C) // in the
    .Then(*ExpectVariableAEquals*, expected_value_1) // Scenario will
        .And(*ExpectVariableBEquals*, expected_value_2) // indicate if it has
        .And(*ExpectVariableCEquals*, expected_value_3) // passed or failed execution.
    .Execute();

現在、BDD構文はより長く、より冗長であり、これらの例はひどく不自然ですが、特定のシステム動作の結果としてシステム内で多くのことが変化する非常に複雑なテスト状況では、BDD構文が明確になります。テスト対象の説明、およびテスト構成がどのように定義されているかについての説明。このコードをプログラマ以外の人に見せれば、彼らは何が起こっているかをすぐに理解できます。さらに、両方の場合で「variable_A」がテストに失敗すると、Assertsサンプルは問題を修正するまで最初のアサートを過ぎて実行されませんが、BDD APIはチェーンで呼び出されるすべてのメソッドを順番に実行し、ステートメントの個々の部分にエラーがありました。

個人的には、このアプローチは、テストの言語が顧客が論理要件を語る言語と同じであるという意味で、従来のxUnitフレームワークよりもはるかに優れていると思います。それでも、私は努力をサポートするための完全なテストAPIを発明する必要なしに、同様のスタイルでxUnitフレームワークを使用することができました。例えば:

Nunitの使用:

[Test]
void TestMyMethod()
{
    const int theExpectedValue = someValue;

    GivenASetupToTestMyMethod();

    var theActualValue = WhenIExecuteMyMethodToTest();

    Assert.That(theActualValue, Is.EqualTo(theExpectedValue)); // nice, but it's not BDD
}

ユニットテストAPIを使用して調査することにした場合、私のアドバイスは、しばらくの間、多数の異なるAPIを実験し、アプローチについて心を開いておくことです。私は個人的にBDDを支持していますが、あなたのビジネスニーズには、チームの状況に応じて異なるものが必要になる場合があります。ただし、重要なのは、既存のシステムの再推測を避けることです。必要に応じて、別のAPIを使用した少数のテストで既存のテストをいつでもサポートできますが、すべてを同じにするために大規模なテストを書き換えることはお勧めできません。レガシコードが使用できなくなると、そのコードとそのテストを新しいコードで簡単に置き換えることができ、代替APIを使用してテストすることができます。これにより、必ずしも真のビジネス価値をもたらさない大きな努力に投資する必要がなくなります。単体テストAPIの使用に関しては、


1

あなたが持っているものはシンプルで、仕事を終わらせます。それがあなたのために働くなら、素晴らしい。主流の単体テストフレームワークは必要ありません。単体テストの既存のライブラリを新しいフレームワークに移植することをsするでしょう。ユニットテストフレームワークの最大の価値は、参入障壁を減らすことだと思います。フレームワークは既に配置されているため、テストの作成を開始するだけです。あなたはその時点を過ぎているので、あなたはその恩恵を受けられません。

主流のフレームワークを使用することのもう1つの利点(マイナーな利点であるIMO)は、新しい開発者が使用しているフレームワークのすべてに既に対応している可能性があるため、トレーニングが少なくて済むことです。実際には、あなたが説明したような単純なアプローチで、これは大したことではないはずです。

また、ほとんどの主流のフレームワークには、フレームワークが持っている場合と持たない場合がある特定の機能があります。これらの機能により配管コードが削減され、テストケースの記述がより速く簡単になります。

  • 命名規則、注釈/属性などを使用したテストケースの自動実行
  • より具体的なさまざまなアサート。すべてのアサーションに対して条件付きロジックを記述したり、型をアサートするために例外をキャッチしたりする必要はありません。
  • テストケースの分類。そのため、それらのサブセットを簡単に実行できます。
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.