web-dev-qa-db-ja.com

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

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

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()メソッドを使用する必要がない場合need、まだ使用する必要がありますか?

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

109
Craig P. Motlin

基本テストテンプレート などのJUnit FAQの例について具体的に疑問に思っている場合、ここで披露されるベストプラクティスはクラスtestは、setUpメソッド(またはテストメソッド)でインスタンス化する必要があります。

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

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

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

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

92
Moss Collum

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

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

42
Craig P. Motlin

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

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

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

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

JUnitsのライフサイクル:

  1. テストメソッドごとに異なるテストクラスインスタンスを作成します
  2. 各testclassインスタンスで繰り返します:setupを呼び出す+ testmethodを呼び出す

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

  • 新しいインスタンスの作成:5718203
  • 新しいインスタンスの作成:5947506
  • セットアップ:5718203
  • TestOne:5718203
  • セットアップ:5947506
  • TestTwo:5947506
18
Jurgen Hannaert

JUnit 4の場合:

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

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

acceptableであり、単純な標準を使用し、常に@Before(エラーをクリアするが冗長)を使用するか、宣言で常に初期化する(簡潔だが混乱するエラーを与える)複雑なコーディング規則に従うのは難しく、これは大したことではありません。

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

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

10
Nils von Barth

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

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

7
dsaff

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

6
amit

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

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

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

5
Alex B

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

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

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

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

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

  • テスト対象のオブジェクトは、設定メソッドで初期化する必要があります。もちろん、今は何も設定しないかもしれませんが、後で設定できます。 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()
         ...
    }
}
0
davidxxx