Azure App ServiceやAzure Functionsを利用したプログラムは、他のJavaプログラムと同様に、JUnitを利用してテスト用プログラムを書くことができる。
今回は、Azure App Service上でAzure Functionsの関数を呼び出しているコントローラクラスと、Azure FunctionsでHTTP要求に応じた結果を返却するハンドラークラスのテスト用プログラムを作成してみたので、共有する。
前提条件
下記記事の実装が完了していること。
また、プログラムの実行結果が以下の記事の通りであること。
結果として、SQLデータベース上のUSER_DATAテーブル、M_SEXテーブルには、以下のデータが入っていることになる。
作成したテスト用サンプルプログラム(App Service側)の内容
作成したサンプルプログラム(App Service側)の構成は以下の通り。
なお、上記の赤枠は、前提条件のプログラムから追加・変更したプログラムである。
pom.xmlは以下の通りで、Spring Boot 2.4.0でJUnit 4のテストクラスを利用するための設定を追加している。
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.4.0</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.example</groupId> <artifactId>demoAzureApp</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>war</packaging> <name>demoAzureApp</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- lombokの設定 --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <scope>provided</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> <scope>provided</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!-- Spring Boot 2.4.0でJUnit 4のテストクラスを利用するための設定 --> <dependency> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.hamcrest</groupId> <artifactId>hamcrest-core</artifactId> </exclusion> </exclusions> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> <plugin> <groupId>com.microsoft.azure</groupId> <artifactId>azure-webapp-maven-plugin</artifactId> <version>1.12.0</version> <configuration> <schemaVersion>v2</schemaVersion> <subscriptionId>(ログインユーザーのサブスクリプションID)</subscriptionId> <resourceGroup>azureAppDemo</resourceGroup> <appName>azureAppDemoService</appName> <pricingTier>B1</pricingTier> <region>japaneast</region> <appServicePlanName>ASP-azureAppDemo-8679</appServicePlanName> <appServicePlanResourceGroup>azureAppDemo</appServicePlanResourceGroup> <runtime> <os>Linux</os> <javaVersion>Java 8</javaVersion> <webContainer>Tomcat 8.5</webContainer> </runtime> <deployment> <resources> <resource> <directory>${project.basedir}/target</directory> <includes> <include>*.war</include> </includes> </resource> </resources> </deployment> </configuration> </plugin> </plugins> </build> </project>
また、テストクラスの内容は以下の通りで、MockMvcを使ってコントローラクラスのメソッド呼び出すと共に、RestTemplateクラスのメソッドをMock化して呼び出している。
package com.example.demo; import static org.springframework.test.web.client.match.MockRestRequestMatchers.content; import static org.springframework.test.web.client.match.MockRestRequestMatchers.method; import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo; import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view; import java.util.ArrayList; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.web.client.MockRestServiceServer; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.web.client.RestTemplate; import com.fasterxml.jackson.databind.ObjectMapper; @RunWith(SpringJUnit4ClassRunner.class) @SpringBootTest public class DemoControllerTest { /** * テスト対象のクラス */ @Autowired private DemoController demoController; /** * テスト対象のクラス内で呼ばれるクラスのMockオブジェクト */ @Autowired private RestTemplate restTemplate; /** * MockMvcオブジェクト */ private MockMvc mockMvc; /** * RestTemplateクラスのメソッドをMock化するためのサーバー */ private MockRestServiceServer mockServer; /** application.propertiesからdemoAzureFunc.urlBaseの値を取得 */ @Value("${demoAzureFunc.urlBase}") private String demoAzureFuncBase; /** * 前処理(各テストケースを実行する前に行われる処理) */ @Before public void init() { // MockMvcオブジェクトにテスト対象メソッドを設定 mockMvc = MockMvcBuilders.standaloneSetup(demoController).build(); // テスト対象のクラスで使用するRestTemplateクラスのメソッドをMock化するためのサーバーを設定 mockServer = MockRestServiceServer.bindTo(restTemplate).build(); } @Test public void testIndex() throws Exception { // テスト対象メソッド(index)を実行 mockMvc.perform(get("/")) // HTTPステータスがOKであることを確認 .andExpect(status().isOk()) // 次画面の遷移先がlist.htmlであることを確認 .andExpect(view().name("list")) // Modelオブジェクトに検索Formが設定されていることを確認 .andExpect(model().attribute("searchForm", new SearchForm())) // Modelオブジェクトにエラーが無いことを確認 .andExpect(model().hasNoErrors()); } @Test public void testSearch() throws Exception { // Azure FunctionsのgetUserDataList関数を呼び出した結果をMock化 mockServer.expect(requestTo(demoAzureFuncBase + "getUserDataList")) .andExpect(method(HttpMethod.POST)) // リクエストヘッダ内容の検証 .andExpect(content() .string("{\"searchName\":[\"\"],\"searchSex\":[\"\"]}")) .andRespond(withSuccess(makeGetUserDataListRes() , MediaType.APPLICATION_JSON)); // テスト対象メソッド(search)を実行 mockMvc.perform(post("/search/") // 検索条件のForm値を設定 .param("searchName", "") .param("searchSex", "")) // HTTPステータスがOKであることを確認 .andExpect(status().isOk()) // 次画面の遷移先がlist.htmlであることを確認 .andExpect(view().name("list")) // Modelオブジェクトの検索Formに // getUserDataList関数を呼び出した結果が設定されていることを確認 .andExpect(model().attribute("searchForm", makeSearchFormRes())) // Modelオブジェクトにエラーが無いことを確認 .andExpect(model().hasNoErrors()); } /** * 返却されるユーザーデータリストを生成 * @return ユーザーデータリスト */ private ArrayList<UserData> makeUserDataList() { ArrayList<UserData> userDataList = new ArrayList<>(); UserData userData = new UserData(); userData.setId("1"); userData.setName("テスト プリン"); userData.setBirthYear("2012"); userData.setBirthMonth("1"); userData.setBirthDay("15"); userData.setSex("女"); userDataList.add(userData); userData = new UserData(); userData.setId("2"); userData.setName("テスト プリン2"); userData.setBirthYear("2013"); userData.setBirthMonth("2"); userData.setBirthDay("16"); userData.setSex("男"); userDataList.add(userData); userData = new UserData(); userData.setId("3"); userData.setName("テスト プリン3"); userData.setBirthYear("2014"); userData.setBirthMonth("3"); userData.setBirthDay("17"); userData.setSex("女"); userDataList.add(userData); return userDataList; } /** * Azure FunctionsのgetUserDataList関数を呼び出した結果となる文字列を生成 * @return 生成した文字列 */ private String makeGetUserDataListRes() { String jsonResponseBody = null; SearchResult searchResult = new SearchResult(); searchResult.setUserDataList(makeUserDataList()); try { ObjectMapper objectMapper = new ObjectMapper(); jsonResponseBody = objectMapper.writeValueAsString(searchResult); } catch (Exception ex) { System.err.println(ex); } return jsonResponseBody; } /** * Azure FunctionsのgetUserDataList関数を呼び出した結果となるFormオブジェクトを生成 * @return 生成したFormオブジェクト */ private SearchForm makeSearchFormRes() { SearchForm resultSearchForm = new SearchForm(); resultSearchForm.setSearchName(""); resultSearchForm.setSearchSex(""); resultSearchForm.setUserDataList(makeUserDataList()); return resultSearchForm; } }
その他のソースコード内容は、以下のサイトを参照のこと。
https://github.com/purin-it/azure/tree/master/azure-junit-test/demoAzureApp
作成したテスト用のサンプルプログラム(Azure Functions側)の内容
作成したサンプルプログラム(Azure Functions側)の構成は以下の通り。
なお、上記の赤枠は、前提条件のプログラムから追加したプログラムである。
<2021年4月13日 追記>
spring-cloud-function-dependenciesのバージョンは、2021年3月16日にリリースしたバージョン3.1.2を利用すると、1つのAzure Functions内に複数のファンクションを含む場合の不具合が解消できている。
その場合、Handlerクラスの継承するクラスを「AzureSpringBootRequestHandler」クラスから「FunctionInvoker」クラスに変更する。
spring-cloud-function-dependenciesの3.1.2を利用した実装サンプルは、以下の記事を参照のこと。
テストクラスの内容は以下の通りで、GetUserDataListHandlerクラスのexecuteメソッドを呼び出した結果を確認している。
package com.example; import static org.junit.Assert.assertEquals; import java.util.ArrayList; import org.junit.Test; import org.springframework.cloud.function.adapter.azure.AzureSpringBootRequestHandler; import com.example.model.SearchForm; import com.example.model.SearchResult; import com.example.mybatis.model.UserData; public class GetUserDataListHandlerTest { /** * GetUserDataListHandlerクラスのexecuteメソッドをテストするメソッド * @throws Exception */ @Test public void executeTest() { // AzureSpringBootRequestHandlerクラスのオブジェクトを生成し、 // GetUserDataListHandlerクラスのexecuteメソッドを実行 AzureSpringBootRequestHandler<SearchForm, SearchResult> handler = new AzureSpringBootRequestHandler<>(DemoAzureFunction.class); SearchResult result = handler.handleRequest(new SearchForm(), null); // AzureSpringBootRequestHandlerクラスのオブジェクトをcloseするのを忘れず行う handler.close(); // 取得内容をコンソールに表示 System.out.println("*** result.getUserDataList()の実行結果 ***"); for(UserData userData : result.getUserDataList()){ System.out.println(userData.toString()); } System.out.println(); // 取得結果を確認 assertEquals(3, result.getUserDataList().size()); assertEquals(makeUserDataList().toString() , result.getUserDataList().toString()); } /** * 返却されるユーザーデータリストを生成 * @return ユーザーデータリスト */ private ArrayList<UserData> makeUserDataList() { ArrayList<UserData> userDataList = new ArrayList<>(); UserData userData = new UserData(); userData.setId("1"); userData.setName("テスト プリン"); userData.setBirthYear("2012"); userData.setBirthMonth("1"); userData.setBirthDay("15"); userData.setSex("女"); userDataList.add(userData); userData = new UserData(); userData.setId("2"); userData.setName("テスト プリン2"); userData.setBirthYear("2013"); userData.setBirthMonth("2"); userData.setBirthDay("16"); userData.setSex("男"); userDataList.add(userData); userData = new UserData(); userData.setId("3"); userData.setName("テスト プリン3"); userData.setBirthYear("2014"); userData.setBirthMonth("3"); userData.setBirthDay("17"); userData.setSex("女"); userDataList.add(userData); return userDataList; } }
なお、Azure Functions側のSpring Bootのバージョンは2.3.4.RELEASEなので、pom.xmlの修正は行っていない。
その他のソースコード内容は、以下のサイトを参照のこと。
https://github.com/purin-it/azure/tree/master/azure-junit-test/demoAzureFunc
サンプルプログラムの実行結果
サンプルプログラムの実行結果は、以下の通り。
1) App Serviceのコントローラのテストを実行した結果は以下の通り。なお、このとき、App Service側のSpring Bootの起動と、Azure Functionsを起動する「mvn azure-functions:run」というコマンドの実行は、共に行っていない。
2) Azure Functionsのテストを実行した結果は以下の通り。なお、このとき、Azure Functionsを起動する「mvn azure-functions:run」というコマンドは実行していない。
要点まとめ
- Azure App ServiceやAzure Functionsを利用したプログラムは、他のJavaプログラムと同様に、JUnitを利用してテスト用プログラムを書くことができる。
- Azure App Serviceのコントローラクラスのテストは、MockMvcを使ってメソッド呼び出したり、RestTemplateクラスのメソッドをMock化して呼び出したりできる。
- Azure FunctionsでHTTP要求に応じた結果を返却するハンドラークラスのテストを行う際は、生成したAzureSpringBootRequestHandlerクラスのオブジェクトをcloseするのを忘れず行う必要がある。