ベストプラクティス:setUp()または宣言でJUnitクラスフィールドを初期化しますか?


120

このような宣言時にクラスフィールドを初期化する必要がありますか?

public class SomeTest extends TestCase
{
    private final List list = new ArrayList();

    public void testPopulateList()
    {
        // Add stuff to the list
        // Assert the list contains what I expect
    }
}

またはこのようにsetUp()で?

public class SomeTest extends TestCase
{
    private List list;

    @Override
    protected void setUp() throws Exception
    {
        super.setUp();
        this.list = new ArrayList();
    }

    public void testPopulateList()
    {
        // Add stuff to the list
        // Assert the list contains what I expect
    }
}

最初のフォームは、より簡潔で、最後のフィールドを使用できるため、よく使用します。セットアップにsetUp()メソッドを使用する必要がない場合でも、それを使用する必要がありますか?その理由は?

明確化: JUnitは、テストメソッドごとに1回、テストクラスをインスタンス化します。つまりlist、どこで宣言したかに関係なく、テストごとに1回作成されます。また、テスト間に一時的な依存関係がないことも意味します。したがって、setUp()を使用する利点はないようです。ただし、JUnit FAQにはsetUp()で空のコレクションを初期化する多くの例があるので、理由があるに違いないと思います。


2
JUnit 4(宣言で初期化)とJUnit 3(setUpを使用)では答えが異なることに注意してください。これが混乱の原因です。
Nils von Barth、

回答:


99

基本的なテストテンプレートなど、JUnit FAQの例について具体的に疑問がある場合は、テスト対象クラスを setUpメソッド(またはテストメソッド)でインスタンス化することをお勧めします。 。

JUnitの例がsetUpメソッドでArrayListを作成すると、testIndexOutOfBoundExceptionやtestEmptyCollectionなどのケースで、それらすべてがそのArrayListの動作をテストします。誰かがクラスを書いて、それが正しく機能することを確認するという視点があります。

独自のクラスをテストする場合も、おそらく同じことを行う必要があります。後でオブジェクトを壊した場合に適切な出力が得られるように、setUpまたはテストメソッドでオブジェクトを作成します。

一方、テストコードでJavaコレクションクラス(または、他のライブラリクラス)を使用している場合、それはおそらくテストしたくないためではなく、テストフィクスチャの一部にすぎません。この場合、それが意図したとおりに機能することを安全に想定できるため、宣言で初期化しても問題ありません。

それだけの価値があるので、私は数年前のTDDで開発されたかなり大きなコードベースに取り組んでいます。私たちはテストコードの宣言で常習的に物事を初期化し、私がこのプロジェクトに携わって1年半の間、問題が発生することはありませんでした。したがって、それが妥当なことであるという事例証拠が少なくともいくつかあります。


45

自分で掘り始めたところ、を使用する潜在的な利点が1つ見つかりましたsetUp()。の実行中に例外がスローされた場合setUp()、JUnitは非常に役立つスタックトレースを出力します。一方、オブジェクトの構築中に例外がスローされた場合、エラーメッセージは単にJUnitがテストケースをインスタンス化できなかったことを示し、JUnitがリフレクションを使用してテストをインスタンス化しているために失敗が発生した行番号が表示されないクラス。

これは空のコレクションを作成する例には当てはまりません。空のコレクションはスローされないためですが、これはsetUp()メソッドの利点です。


18

アレックスBの答えに加えて。

特定の状態でリソースをインスタンス化するには、setUpメソッドを使用する必要もあります。コンストラクターでこれを行うことは、タイミングの問題だけでなく、JUnitがテストを実行する方法のために、テストの実行後に各テスト状態が消去されます。

JUnitは最初に各テストメソッドのtestClassのインスタンスを作成し、各インスタンスの作成後にテストの実行を開始します。テストメソッドを実行する前に、そのセットアップメソッドが実行され、いくつかの状態を準備できます。

データベースの状態がコンストラクターで作成される場合、すべてのインスタンスは、各テストを実行する前に、お互いの直後にdbの状態をインスタンス化します。2番目のテスト以降、テストはダーティ状態で実行されます。

JUnitsライフサイクル:

  1. テストメソッドごとに異なるtestclassインスタンスを作成する
  2. testclassインスタンスごとに繰り返します。セットアップを呼び出し、テストメソッドを呼び出します。

2つのテストメソッドを使用したテストでいくつかのログを取得すると、次のようになります(数値はハッシュコードです)。

  • 新しいインスタンスの作成:5718203
  • 新しいインスタンスの作成:5947506
  • 設定:5718203
  • TestOne:5718203
  • 設定:5947506
  • TestTwo:5947506

3
正解ですが、トピックから外れています。データベースは基本的にグローバルな状態です。これは私が直面している問題ではありません。適切に独立したテストの実行速度だけに関心があります。
クレイグP.モトリン

この初期化の順序は、JUnit 3にのみ当てはまります。重要な注意事項です。JUnit 4では、テストインスタンスは遅延して作成されるため、宣言またはセットアップメソッドでの初期化は、テスト時に行われます。また、1回限りのセットアップのために、一つは使用することができる@BeforeClassのJUnit 4で
ニルス・フォン・バルト

11

JUnit 4の場合:

  • テスト対象クラスの場合は、@Beforeメソッドで初期化して、失敗をキャッチします。
  • 以下のための他のクラス、宣言で初期化...
    • ...簡潔にするため、およびフィールドをマークするため final質問で述べたとおりにために、
    • ... 失敗する可能性のある複雑な初期化である場合を除いて@Before、失敗をキャッチするためにを使用します。
  • 以下のためのグローバルな状態(特に低速の初期化データベースのような、)、使用@BeforeClass、しかし、注意してくださいテスト間の依存関係。
  • もちろん、単一のテストで使用されるオブジェクトの初期化は、テストメソッド自体で行う必要があります。

@Beforeメソッドまたはテストメソッドで初期化することで、障害に関するより適切なエラーレポートを取得できます。これは、テスト中のクラス(壊れる可能性があります)のインスタンス化に特に役立ちますが、ファイルシステムアクセス(「ファイルが見つかりません」)やデータベースへの接続(「接続拒否」)などの外部システムの呼び出しにも役立ちます。

単純な標準を持ち、常に使用することは許容されます@Before複雑なコーディングルールに従うのは難しく、これは大した問題ではないため(エラーをクリアするが詳細)または宣言で初期化する(簡潔だが混乱するエラーを与える)。

inの初期化setUpはJUnit 3の遺物であり、すべてのテストインスタンスが熱心に初期化され、高価な初期化を行うと問題(速度、メモリ、リソースの枯渇)が発生します。したがって、ベストプラクティスはsetUp、テストを実行するときにのみ実行される、で高額な初期化を行うことでした。これはもはや適用されないので、を使用する必要ははるかに少なくなりますsetUp

これは、特にCraig P. Motlin(質問自体と自己回答)、Moss Collum(テスト中のクラス)、およびdsaffによる、Ledeを埋める他のいくつかの応答を要約しています。


7

JUnit 3では、フィールド初期化子は、テストが実行される前に、テストメソッドごとに1回実行されます。メモリ内のフィールド値が小さく、設定にかかる時間がほとんどなく、グローバル状態に影響を与えない限り、フィールド初期化子を使用することは技術的に問題ありません。ただし、これらの条件が満たされない場合、最初のテストが実行される前に、多くのメモリを消費するか、フィールドの設定に時間がかかり、メモリ不足になる可能性もあります。このため、多くの開発者は常に、厳密に必要でない場合でも、常に安全なsetUp()メソッドでフィールド値を設定します。

JUnit 4では、テストオブジェクトの初期化はテスト実行の直前に行われるため、フィールド初期化子を使用する方が安全であり、推奨されるスタイルです。


面白い。最初に説明した動作はJUnit 3にのみ適用されるのですか?
クレイグP.モトリン

6

あなたの場合(リストを作成する)実際には違いはありません。しかし、JUnitが例外を正しく報告するのに役立つので、通常はsetUp()を使用する方が適切です。テストのコンストラクタ/イニシャライザで例外が発生した場合、それはテストの失敗です。ただし、セットアップ中に例外が発生した場合、それをテストのセットアップにおける何らかの問題と考えるのは自然であり、junitはそれを適切に報告します。


1
よく言った。常にsetUp()でインスタンス化するのに慣れれば、心配する必要のある質問が1つ少なくなります。たとえば、どこにfooBarをインスタンス化するべきか、どこにコレクションを置くべきかなどです。これは、準拠する必要のある一種のコーディング標準です。リストではなく、他のインスタンス化によってメリットがあります。
オラフコック

@Olafコーディング標準についての情報をありがとう、私はそれについて考えていませんでした。私はモス・カラムのコーディング標準の考え方にもっと同意する傾向があります。
Craig P. Motlin、2009

5

私は読みやすさを優先しますが、ほとんどの場合、セットアップ方法を使用しません。基本的なセットアップ操作に時間がかかり、各テストで繰り返される場合は例外です。
その時点で、@BeforeClassアノテーションを使用してその機能をセットアップメソッドに移動します(後で最適化します)。

@BeforeClasssetupメソッドを使用した最適化の例:データベース機能テストの一部にdbunitを使用しています。セットアップ方法は、データベースを既知の状態にすることを担当します(非常に遅い...データ量に応じて30秒-2分)。注釈が付けられたセットアップメソッドでこのデータを読み込み、@BeforeClass各テスト内でデータベースを再読み込み/初期化するのではなく、同じデータセットに対して10〜20のテストを実行します。

Junit 3.8(例に示すようにTestCaseを拡張)を使用するには、注釈を追加するだけではなく、もう少しコードを書く必要がありますが、「クラス設定の前に1回実行」することも可能です。


1
+1は読みやすさを優先するためです。ただし、2番目の方法が最適化であるとはまったく確信していません。
クレイグP.モトリン

@Motlinセットアップで最適化する方法を明確にするために、dbunitの例を追加しました。
アレックスB

データベースは基本的にグローバルな状態です。したがって、dbセットアップをsetUp()に移動することは最適化ではなく、テストが正しく完了する必要があります。
Craig P. Motlin 09

@Alex B:Motlinが言ったように、これは最適化ではありません。あなたはコードのどこで初期化が行われるかを変更しているだけで、何回もどれほど速くも変更していません。
エディ

「@BeforeClass」アノテーションの使用を暗示するつもりでした。例を編集して明確にします。
アレックスB

2

各テストは独立して実行され、オブジェクトの新しいインスタンスがあるためsetUp()、個々のテストとの間で共有されるものを除いて、内部状態を持つTestオブジェクトにはあまり意味がありませんtearDown()。これは、(他の人が与えた理由に加えて)setUp()メソッドを使用するのが良い理由の1つです。

注:JUnitテストオブジェクトが静的な状態を維持することはお勧めできません。追跡または診断以外の目的でテストで静的変数を使用する場合、JUnitの目的の一部が無効になります。つまり、テストは任意の順序で実行でき、各テストは新鮮できれいな状態。

使用する利点setUp()は、すべてのテストメソッドで初期化コードをカットアンドペーストする必要がないこと、およびコンストラクターにテストセットアップコードがないことです。あなたの場合、ほとんど違いはありません。空のリストを作成するだけで、それを表示したり、コンストラクターで簡単に初期化したりできます。ただし、あなたや他の人が指摘したように、失敗する可能性がある場合は診断スタックダンプを取得Exceptionできるように、スローする可能性のあるものはすべて実行する必要がsetUp()あります。

空のリストを作成しているだけの場合、私はあなたが提案しているのと同じ方法で行います:宣言の時点で新しいリストを割り当てます。特に、この方法finalでは、テストクラスにとって意味がある場合にマークを付けることができます。


1
+1は、オブジェクトの構築中にリストを最終的にマークするための初期化を実際にサポートする最初の人物であるためです。ただし、静的変数に関することは、質問とは関係ありません。
Craig P. Motlin 09

@Motlin:true、静的変数に関することは少し話題から外れています。なぜそれを追加したのかはわかりませんが、当時は適切であるように思われました。これは、最初の段落で言っていたことを拡張したものです。
エディ

の利点はfinal質問にも記載されています。
Nils von Barth、

0
  • 定数値(フィクスチャまたはアサーションでの使用)は、宣言で初期化する必要があり、final(決して変更しないように)

  • テスト対象のオブジェクトは、設定する可能性があるため、setupメソッドで初期化する必要があります。もちろん、今は設定しないこともありますが、後で設定することもできます。initメソッドでインスタンス化すると、変更が容易になります。

  • これらがモックされている場合、テスト対象のオブジェクトの依存関係は、自分でインスタンス化することすらできません。現在、モックフレームワークはリフレクションによってオブジェクトをインスタンス化できます。

モックに依存しないテストは次のようになります。

public class SomeTest {

    Some some; //instance under test
    static final String GENERIC_ID = "123";
    static final String PREFIX_URL_WS = "http://foo.com/ws";

    @Before
    public void beforeEach() {
       some = new Some(new Foo(), new Bar());
    } 

    @Test
    public void populateList()
         ...
    }
}

分離する依存関係のあるテストは次のようになります。

@RunWith(org.mockito.runners.MockitoJUnitRunner.class)
public class SomeTest {

    Some some; //instance under test
    static final String GENERIC_ID = "123";
    static final String PREFIX_URL_WS = "http://foo.com/ws";

    @Mock
    Foo fooMock;

    @Mock
    Bar barMock;

    @Before
    public void beforeEach() {
       some = new Some(fooMock, barMock);
    }

    @Test
    public void populateList()
         ...
    }
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.