Azure基本

Azure App ServiceやAzure FunctionsをJUnitでテストをしてみた

Azure App ServiceやAzure Functionsを利用したプログラムは、他のJavaプログラムと同様に、JUnitを利用してテスト用プログラムを書くことができる。

今回は、Azure App Service上でAzure Functionsの関数を呼び出しているコントローラクラスと、Azure FunctionsでHTTP要求に応じた結果を返却するハンドラークラスのテスト用プログラムを作成してみたので、共有する。

前提条件

下記記事の実装が完了していること。

Azure App ServiceからAzure FunctionsにPost送信してみた(ソースコード編)今回も引き続き、Azure App ServiceからPost通信によってAzure Functionsを呼び出す処理の実装について述べ...

また、プログラムの実行結果が以下の記事の通りであること。

Azure App ServiceからAzure FunctionsにPost送信してみた(前提条件と実行結果)Azure App ServiceからAzure Functionsを呼び出す際、Get通信だけでなく、リクエストパラメータを含むPos...

結果として、SQLデータベース上のUSER_DATAテーブル、M_SEXテーブルには、以下のデータが入っていることになる。
前提条件_1

前提条件_2

作成したテスト用サンプルプログラム(App Service側)の内容

作成したサンプルプログラム(App Service側)の構成は以下の通り。
サンプルプログラムの構成(AppService側)
なお、上記の赤枠は、前提条件のプログラムから追加・変更したプログラムである。

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



フリエン(furien)は多くの案件を保有しフリーランス向けサービスも充実しているエージェントだったフリエン(furien)は、ITフリーランス(個人事業主)エンジニア専門のエージェントであるアン・コンサルティング株式会社が運営する業界...

作成したテスト用のサンプルプログラム(Azure Functions側)の内容

作成したサンプルプログラム(Azure Functions側)の構成は以下の通り。
サンプルプログラムの構成(AzureFunctions)
なお、上記の赤枠は、前提条件のプログラムから追加したプログラムである。

<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を利用した実装サンプルは、以下の記事を参照のこと。

spring-cloud-function-dependenciesのバージョンを最新(3.1.2)にしてみたこれまでこのブログで取り上げてきたAzure Functionsのサンプルプログラムでは、spring-cloud-function-d...

テストクラスの内容は以下の通りで、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



「CODE×CODE」は、需要の高い技術(AWS, Python等)を習得できるプログラミングスクールスクールだった近年、さまざまな会社でクラウド(特にIaaSやPaaSのパブリッククラウド)の需要が非常に高まっていて、クラウドサービスによるシステム開...

サンプルプログラムの実行結果

サンプルプログラムの実行結果は、以下の通り。

1) App Serviceのコントローラのテストを実行した結果は以下の通り。なお、このとき、App Service側のSpring Bootの起動と、Azure Functionsを起動する「mvn azure-functions:run」というコマンドの実行は、共に行っていない。
サンプルプログラムの実行結果_1_1

サンプルプログラムの実行結果_1_2

2) Azure Functionsのテストを実行した結果は以下の通り。なお、このとき、Azure Functionsを起動する「mvn azure-functions:run」というコマンドは実行していない。
サンプルプログラムの実行結果_2_1

サンプルプログラムの実行結果_2_2

要点まとめ

  • Azure App ServiceやAzure Functionsを利用したプログラムは、他のJavaプログラムと同様に、JUnitを利用してテスト用プログラムを書くことができる。
  • Azure App Serviceのコントローラクラスのテストは、MockMvcを使ってメソッド呼び出したり、RestTemplateクラスのメソッドをMock化して呼び出したりできる。
  • Azure FunctionsでHTTP要求に応じた結果を返却するハンドラークラスのテストを行う際は、生成したAzureSpringBootRequestHandlerクラスのオブジェクトをcloseするのを忘れず行う必要がある。