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のテストクラスを利用するための設定を追加している。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 | <?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化して呼び出している。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 | 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メソッドを呼び出した結果を確認している。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 | 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するのを忘れず行う必要がある。