JUnit単体テストアシスタント(UTA)でSpringのテストを好きになる

(この記事は、開発元Parasoft社 Blog 「Start to Love Spring Testing with the Unit Test Assistant (UTA) for Java」2017年11月28日の翻訳記事です。)

Springフレームワーク (およびSpring Boot )は、最も人気のある エンタープライズ向けJavaフレームワークの1つです。Springがミッションクリティカルなアプリケーションで使用されるということは、品質とセキュリティが厳しく求められるようになってきているということです。以前の記事で 、単体テストの品質改善効果は実証済みにもかかわらず、開発者は単体テストを好きではないという問題を取り上げ、Parasoft Jtestの単体テストアシスタントが提供する自動化とガイド機能によって、テストがより楽しくなるだけでなく、より容易で効率的になることを説明しました。この記事では、Springフレームワークについて同じテーマで取扱い、この重要なアプリケーションフレームワークでも自動化およびガイド機能が活用できることを示します。ここからは、Jtestの単体テストアシスタントをUTA という略語で呼びます。

Springアプリケーションのテストの課題

Springフレームワークは、 そのままでも統合テストをサポートしていますが、テストケースを適切にセットアップするには多くの手作業によるコーディングが必要です。Springアプリケーションのテストを構築し、メンテナンスしていくうえで、開発者は次のような特有の課題に直面します。

  • Springフレームワークを初期化して設定する必要がある
  • 通常、アプリケーションは、サードパーティのライブラリ(永続ストレージ、外部サービスなど)に依存している
  • アプリケーションでは、セッション、セキュリティ、メッセージングなどの処理に、組み込みのSpring機能が使用されていることがよくある。これらは、Springテストに慣れていない開発者にとっては設定が難しい場合がある
  • アプリケーションの依存関係(Beanなど)を適切に構成する必要がある

これらの課題に加えて、包括的でメンテナンス性の高いテストスイートを作成するには時間がかかるため、開発者が十分なテストを作成できないという結果になりがちですそれが最終的には、セキュリティ上の脆弱性、欠陥、レグレッションなど、多くの悩ましい問題につながります。

UTAは、JUnitテストの生成、改良、およびメンテナンスのプロセスをはるかに簡単にし、時間を短縮するのに役立ち、開発者が優れたテストをすばやく作成し、本来やりたかったはずの作業―つまりコードの作成に戻れるよう支援します。

Spring MVCテストフレームワーク

Spring Frameworkは、コントローラー、サービス、およびその他のコンポーネントのテストをはるかに簡単にするテストフレームワークを備えています。これには、Springテストコンテナーのコンフィギュレーション、 Controllerハンドラーメソッドの呼び出し、カスタムアサーションによる動作の検証のための機能が含まれます。

Spring MVCコントローラーの例:

@Controller
public class TodoController {
 
    @Autowired
    private TodoService service;
    @GetMapping("/")
    public String findAll(Model model) {
        List<Todo> todos = service.findAll();
        model.addAttribute("todos", todos);
        return "todo/list";
    }
}

 

この例のコントローラーは、”To Do”リストから項目を取得する単純なRESTサービスを実装します。コントローラ―は、ビジネスロジックを含むTodoServiceに依存しています。

findAllメソッドをテストするには、以下を実行するJunitテストが必要です。

  • テスト対象のControllerと、Controllerが依存するTodoServiceを使用してSpringコンテナーを設定する
  • findAllハンドラーメソッドに有効なリクエストを送信する
  • 戻り値( “todo/list”)とモデル属性 “todos”を含むレスポンスの要素を検証する

Spring MVCのJunitテストの例は、次のようになります。

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class TodoControllerTest {
    @Autowired
    TodoController controller;
 
    @Autowired
    TodoService todoService;
 
    MockMvc mockMvc;
 
    @Before
    public void setup() {
        mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
    }
 
    @Configuration
    static class Config {
        @Bean
        public TodoController getTodoController() {
            return new TodoController();
        }
 
        @Bean
        public TodoService getTodoService() {
            return new TodoService();
        }
    }
 
    @Test
    public void testFindAll() throws Exception {
        mockMvc.perform(get(“/”)).andExpect(view().name(“todo/list”));
    }
}
 

上記の例は非常に単純なテストですが、それでもたくさんの定型的なコードを書く必要があり、多くの処理を行っています。この例では、内部的なConfigurationクラスを使用して、コントローラとそのサービスでSpringをコンフィギュレーションしています。次に、 MockMvc関数を使用してハンドラーメソッドにリクエストを送信し( performを使用)、 andExpectを使用して返されたビュー名を検証します。

上記のテストの何が悪いのでしょうか?何も悪くはありません――でも、コントローラーメソッドがもっと複雑だったらと想像してみてください。複数のハンドラーメソッドがあり、より多くの引数を受け取り、より多くの出力を生成するような場合です。テストを書くには、特に高いテストカバレッジを得るのが重要な場合は、ずっと多くの時間がかかるでしょう。さらに、ほとんどの現実のテストでは、もっといろいろなコンフィギュレーション(XMLまたはクラスコンフィギュレーション、セッションおよび環境、セキュリティなど)が必要です。

 

UTAによるSpringテストの生成

単体テストアシスタントは、開発者がSpringテストを作成するのをさまざまな手段で支援します。

  1. Spring MVCテストのための定型コードを自動生成
  2. テストカバレッジを増加させるため、パラメーター化されたテストを自動生成
  3. 依存関係をモックしてヘルパーメソッドを分離し、テストを単純化する
  4. 実行時のカバレッジデータの収集とテストフローの分析
  5. テストを改善するための推奨事項をクイックフィックスとともに提示

テストの自動生成

UTAでSpringテストを生成するのは簡単です。IDEでコントローラーのSpringハンドラーメソッドを選択し、テスト作成アクションを選択するだけです。

UTA Spring Blog Part1 - Building Tests pic1.png

Regular SpringまたはParameterized Springのいずれかを選択すると、Configurationクラス(およびコントローラが依存するすべてのBean)を含む、定型的なSpring MVCテストが自動生成されます。mockMvc.performコールも追加され、テスト作成時に選択したハンドラーメソッドを呼び出すように事前設定されています。UTA は、コメントとしていくつかのアサーション例も追加するので、コメントを解除して設定することで利用できます。

UTAは、環境設定で [ContextConfiguration attributes for Spring tests] オプションを設定すると、XMLまたはクラスコンフィギュレーションを使用してテストを生成できます。

依存関係のモック化

単体テストを行ううえで、複雑で手間がかかるのは、主にテスト対象のユニットを分離する作業であるため、依存関係をいかに制御するかは重要です。 UTAはデフォルトでMockitoPowerMockitoを使って依存関係をモック化します(必要がなければ、モックを無効にすることもできます)。依存関係をモック化することで、テストでこれらの依存関係を制御し、アプリケーションの他の部分からハンドラーメソッドを分離でき、ハンドラーをテストすることに集中できます。上の例のハンドラーでは、 TodoServiceに対してfindAllメソッドが呼び出されています。実際のTodoServiceを使用すると、事実上、 TodoControllerとTodoServiceの両方をテストすることになります。このようなテストは、統合テストでは必要かもしれませんが、単体テストでは不要です。テストでTodoService.findAllのレスポンスをモック化すると、ハンドラーメソッドのテストに集中できます。

(Springテストでの依存関係のモック化について、さらに詳しく知りたいというご要望があれば、今後の投稿で取り上げたいと思います)。

Spring Boot

Spring BootはBeanの設定を簡単にするほか、テスト用のアノテーションを追加するので、UTAは、プロジェクト内でSpring Bootを検出すると、通常とは若干異なるテストを生成します。例えば、MockMvcはautowireされ、依存関係は@MockBeanでモック化され、@SpringBootTestアノテーションが使われます。

テストの実行と結果の分析

UTAによって生成されたテストは、通常のJunitランナーを使用して実行できます。UTAには、Junitを実行してテストを分析するツールバーアクションがあります。

UTA Spring Blog Part1 - Building Tests pic2.png

テスト実行後、テストの実行フローが表示され、テストを改善するための推奨事項がUTAによって作成され、IDE内に表示されます。

UTA Spring Blog Part1 - Building Tests pic3.png

ハンドラーメソッドの入力値の提供

ハンドラーメソッドは、メソッドの引数としてパス、クエリー、または他のパラメーターを受け取ることがよくあります。MVCハンドラーメソッドをテストする際、MockMvcを使用して、メソッドを呼び出すために必要なパス/クエリーおよびその他のパラメーターを構築できます。

UTAは、mockMvc.performコールを自動設定してハンドラーメソッドを呼び出します。個々のパラメーターは、ローカル変数として(パラメータライズされたテストの場合はパラメーターとして)テストに追加されるので、テストが適切に動作するように値を設定する必要があります。

例:

@Test public void testGetPerson() throws Throwable {     // When
    String id = ""; // UTA: Configure an appropriate parameter value since the tested method depends on it     ResultActions actions = mockMvc.perform(get("/people/" + id));

ここでは、String ” id ” を設定する必要があります。設定しない場合、使用されるパスは “/ people /”になり、Springは指定されたパスと適切なハンドラーメソッドをマッチさせることができません。

UTAは、さまざまなタイプのハンドラーメソッドパラメーターを検索し、次の方法で自動的にテストを準備します。

  • HttpSession( setAttribute()の呼び出しのサンプルを追加)
  • ヘッダー( header()の呼び出しを追加)
  • リクエスト ボディ(ペイロード変数およびcontent()の呼び出しを追加)
  • 認証( setupメソッドにインスタンス化のサンプルを追加。またprincipal()の呼び出しを追加)

ハンドラーメソッドを呼び出さないテストを実行すると、次のような推奨事項が生成されます。

UTA Spring Blog Part1 - Building Tests pic4.png

ハンドラーメソッドの出力の検証

ハンドラーメソッドが呼び出し元に提供するものに応じて、さまざまな型が返されることがあります。ほとんどの場合、ハンドラーメソッドは、 ModelAndView (またはModelやRedirectViewのような類似のオブジェクト)を返してページを提供したり、ある種のResponseEntity (時にはシリアル化される生のオブジェクトのみ)を返します。Spring MVC Testフレームワークは、このレスポンスにアクセスして検証できます。

たとえば、 ModelAndViewを返すハンドラーメソッドに対して、UTAが次のアサーションを追加したとします。

  // When
  String id = “1”;
  ResultActions actions = mockMvc.perform(get(“/people/” + id));
 
  // Then
  // actions.andExpect(status().isOk());
  // actions.andExpect(header().string(“”, “”));
  // actions.andExpect(view().name(“”));
  // actions.andExpect(model().attribute(“”, “”));

テストが生成された後、これらのアサーションのコメントを外して値を入力することで、有効で意味のあるテストをすばやく作成できます。 実行時にアサーションが失敗すると、UTAは推奨事項とともに、自動的に期待値を更新するか、あるいはアサーションを削除するためのクイックフィックスを提示します。アサーションに適切な値をすばやく設定するには、アサーションのコメントを外し、アサーションを失敗させてから、クイックフィックスを使用して正しい期待値を設定するとよいでしょう。

まとめ

Spring(およびSpring Boot)は、エンタープライズJavaアプリケーションの主要なフレームワークであり、適切なレベルのテストを行って、品質とセキュリティを確保する必要があります。しかし残念なことに、現在のところ、要求されるレベルのテストは達成されていません。それは主に、時間が不足していること、手作業によるコーディングとメンテナンスが必要であることが原因です。Jtest の単体テストアシスタント は単体テストの実行を自動化するだけでなく、テスト作成をガイドしたり、依存関係を制御することによって、テストの作成を加速し、メンテナンスの負担を軽減します。

 

Parasoft Jtestについて

Java対応静的解析・単体テストツール Parasoft Jtest

Jtestは、テスト工数の大幅削減とセキュアで高品質なJavaシステムの開発を強力にサポートするJava対応テストツールです。1,000個以上のコーディング規約をもとにソースコードを静的に解析し、プログラムの問題点や処理フローに潜む検出困難なエラーを検出します。さらに、JUnitを用いた単体テストについて、作成、実行、テストカバレッジ分析、テスト資産の管理といった単体テストに係る作業をサポートし、単体テストの効率化を促進します。

Top