9

最近のプロジェクトでは、Sonarはテストカバレッジが弱いことに不満を持っていました。デフォルトでは統合テストを考慮していないことに気づきました。Sonarを構成できるため、それらを考慮します(JaCoCoプラグイン)という事実に加えて、統合テストですべてのサービスとデータベースレイヤーをカバーするときに、ユニットテストを作成する必要があるかどうかをチームで話し合っていました。とりあえず。

統合テストで私が意味するのは、すべてのテストが、本番環境で使用するのと同じタイプの専用のOracleインスタンスに対して実行されるということです。私たちは何も嘲笑しません。サービスが別のサービスに依存している場合は、実際のサービスを使用します。テストを実行する前に必要なデータは、サービス/リポジトリ(DAO)を使用するいくつかのファクトリクラスを介して構築されます。

したがって、私の観点からは、特にSpring Data / Hibernateのようなフレームワークを使用する場合に、単純なCRUD操作の統合テストを作成することは大きな労力ではありません。何をどのようにモックするかを考えていないので、さらに簡単な場合もあります。

では、作成できる統合テストほど信頼性の低いCRUD操作の単体テストを作成する必要があるのはなぜですか?

私が見る唯一のポイントは、統合テストの実行には時間がかかり、プロジェクトが大きくなるということです。したがって、チェックイン前にすべてを実行する必要はありません。しかし、これがそれほど悪いかどうかはわかりません。Jenkins/ Hudsonを使用したCI環境があれば、その仕事をこなすことができます。

だから-どんな意見や提案も大歓迎です!

4

3 に答える 3

11

ほとんどのサービスが単にdaosに渡され、daosがほとんど何もしないがSpringのメソッドを呼び出す場合、HibernateTemplateまたはJdbcTemplateユニットテストは統合テストがすでに証明していることを実際には証明しないというのは正しいことです。ただし、通常の理由から、単体テストを実施することは価値があります。

単体テストは単一のクラスのみをテストし、ディスクやネットワークにアクセスせずにメモリ内で実行し、実際に一緒に動作する複数のクラスをテストすることはないため、通常は次のようになります。

  • サービスユニットテストは、daosをモックします。
  • Daoユニットテストは、データベースドライバー(またはSpringテンプレート)をモックするか、組み込みデータベースを使用します(Spring 3では非常に簡単です)。

daoに渡されるサービスを単体テストするには、次のようにモックすることができます。

@Before
public void setUp() {
    service = new EventServiceImpl();
    dao = mock(EventDao.class);
    service.EventDao = dao;
}

@Test
public void creationDelegatesToDao() {
    service.createEvent(sampleEvent);
    verify(dao).createEvent(sampleEvent);
}

@Test(expected=EventExistsException.class)
public void creationPropagatesExistExceptions() {
    doThrow(new EventExistsException()).when(dao).createEvent(sampleEvent);
    service.createEvent(sampleEvent);
}

@Test
public void updatesDelegateToDao() {
    service.updateEvent(sampleEvent);
    verify(dao).updateEvent(sampleEvent);
}

@Test
public void findingDelgatesToDao() {
    when(dao.findEventById(7)).thenReturn(sampleEvent);
    assertThat(service.findEventById(7), equalTo(sampleEvent));

    service.findEvents("Alice", 1, 5);
    verify(dao).findEventsByName("Alice", 1, 5);

    service.findEvents(null, 10, 50);
    verify(dao).findAllEvents(10, 50);
}

@Test
public void deletionDelegatesToDao() {
    service.deleteEvent(sampleEvent);
    verify(dao).deleteEvent(sampleEvent);
}

しかし、これは本当に良い考えですか?これらのMockitoアサーションは、daoメソッドが呼び出されたと主張しており、期待どおりに実行されたとは主張していません。あなたはあなたのカバレッジ数を得るでしょう、しかしあなたは多かれ少なかれあなたのテストをdaoの実装に拘束しています。痛い。

この例では、サービスに実際のビジネスロジックがないと想定しています。通常、サービスにはdao呼び出しに加えてビジネスロジックがあり、それらをテストする必要があります。

さて、daosの単体テストには、組み込みデータベースを使用するのが好きです。

private EmbeddedDatabase database;
private EventDaoJdbcImpl eventDao = new EventDaoJdbcImpl();

@Before
public void setUp() {
    database = new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.H2)
            .addScript("schema.sql")
            .addScript("init.sql")
            .build();
    eventDao.jdbcTemplate = new JdbcTemplate(database);
}

@Test
public void creatingIncrementsSize() {
    Event e = new Event(9, "Company Softball Game");

    int initialCount = eventDao.findNumberOfEvents();
    eventDao.createEvent(e);
    assertThat(eventDao.findNumberOfEvents(), is(initialCount + 1));
}

@Test
public void deletingDecrementsSize() {
    Event e = new Event(1, "Poker Night");

    int initialCount = eventDao.findNumberOfEvents();
    eventDao.deleteEvent(e);
    assertThat(eventDao.findNumberOfEvents(), is(initialCount - 1));
}

@Test
public void createdEventCanBeFound() {
    eventDao.createEvent(new Event(9, "Company Softball Game"));
    Event e = eventDao.findEventById(9);
    assertThat(e.getId(), is(9));
    assertThat(e.getName(), is("Company Softball Game"));
}

@Test
public void updatesToCreatedEventCanBeRead() {
    eventDao.createEvent(new Event(9, "Company Softball Game"));
    Event e = eventDao.findEventById(9);
    e.setName("Cricket Game");
    eventDao.updateEvent(e);
    e = eventDao.findEventById(9);
    assertThat(e.getId(), is(9));
    assertThat(e.getName(), is("Cricket Game"));
}

@Test(expected=EventExistsException.class)
public void creatingDuplicateEventThrowsException() {
    eventDao.createEvent(new Event(1, "Id1WasAlreadyUsed"));
}

@Test(expected=NoSuchEventException.class)
public void updatingNonExistentEventThrowsException() {
    eventDao.updateEvent(new Event(1000, "Unknown"));
}

@Test(expected=NoSuchEventException.class)
public void deletingNonExistentEventThrowsException() {
    eventDao.deleteEvent(new Event(1000, "Unknown"));
}

@Test(expected=NoSuchEventException.class)
public void findingNonExistentEventThrowsException() {
    eventDao.findEventById(1000);
}

@Test
public void countOfInitialDataSetIsAsExpected() {
    assertThat(eventDao.findNumberOfEvents(), is(8));
}

ほとんどの人が統合テストと呼ぶかもしれませんが、私はまだこれを単体テストと呼んでいます。組み込みデータベースはメモリ内にあり、テストの実行時に起動および停止されます。ただし、これは、組み込みデータベースが本番データベースと同じように見えるという事実に依存しています。そうなるのでしょうか?そうでなければ、そのすべての作業はかなり役に立たなかった。もしそうなら、あなたが言うように、これらのテストは統合テストとは異なることをしています。しかし、私はそれらをオンデマンドで実行することができmvn test、リファクタリングする自信があります。

そのため、とにかくこれらの単体テストを作成し、カバレッジ目標を達成します。統合テストを作成するとき、HTTPリクエストが期待されるHTTPレスポンスを返すことを主張します。ええ、それはユニットテストを含みます、しかしねえ、あなたがTDDを練習するとき、あなたはとにかくあなたの実際のdao実装の前にそれらのユニットテストを書いています。

daoの後に単体テストを作成する場合は、もちろん、作成するのは楽しいことではありません。TDDの文献には、コードが機能するように感じられ、誰もそれをやりたくないと感じた後にテストを書く方法についての警告がたくさんあります。

TL; DR:統合テストは単体テストを包含します。その意味で、単体テストは実際のテスト値を追加していません。ただし、カバレッジの高いユニットテストスイートを使用している場合は、リファクタリングを行う自信があります。しかしもちろん、daoがSpringのデータアクセステンプレートを簡単に呼び出している場合は、リファクタリングを行っていない可能性があります。しかし、あなたは決して知りません。そして最後に、ユニットテストが最初にTDDスタイルで記述されている場合は、とにかくそれらを使用することになります。

于 2012-09-18T00:39:00.620 に答える
1

レイヤーをプロジェクト外の他のコンポーネントに公開する予定がある場合にのみ、各レイヤーを個別に単体テストする必要があります。Web アプリの場合、リポジトリ レイヤーを呼び出す唯一の方法はサービス レイヤーによるものであり、サービス レイヤーを呼び出す唯一の方法はコントローラー レイヤーによるものです。したがって、テストはコントローラー層で開始および終了できます。バックグラウンド タスクの場合、これらはサービス レイヤーで呼び出されるため、ここでテストする必要があります。

最近では、実際のデータベースを使用したテストは非常に高速であるため、セットアップ/ティアダウンを適切に設計すれば、テストがそれほど遅くなることはありません。ただし、速度が遅い、または問題のある依存関係が他にある場合は、それらをモック/スタブする必要があります。

このアプローチにより、次のことが得られます。

  • 良いカバレッジ
  • リアリスティックテスト
  • 最低限の労力
  • 最小限のリフレクト作業

ただし、レイヤーを分離してテストすることで、チームがより並行して作業できるようになるため、1 つの開発者がリポジトリを実行し、別の開発者が 1 つの機能のサービスを実行し、個別にテストされた作業を生成できます。

Selenium/機能テストが組み込まれている場合、実行するには遅すぎるため、これらだけに頼ることはできないため、常に二重のカバレッジがあります。ただし、機能テストは必ずしもすべてのコードをカバーする必要はありません。コードが単体/統合テストでカバーされている限り、コア機能だけで十分です。

于 2012-09-18T01:09:46.937 に答える