チャットbotお試し用アプリ開発_7日目

# 目次
・本日の成果・考え
・最後に


### 本日の成果
前回作成した設定ファイルDtoをもとにUTのテストパターンおよびテストソースを作成しました。

まずはテストパターンから

1. 正常系　インスタンスの生成および取得処理が正しく動作する
1. 異常系　生成処理の引数がNULLの場合、エラーとなる
1. 異常系　生成処理の引数にから文字を渡すとエラーとなる
1. 異常系　生成処理を実行せずに取得処理を実行しエラーとなる

::: note info
生成処理の引数および空文字チェックは、String型のみ実施しようと思います。
int型はJavaのコンパイル時点でエラーとなり、生成処理を呼び出すAppSettingLoaderクラスの時点で、渡す値がint型になっているので設計上int型以外は生成処理の引数として渡されないはずです。
※int型の引数チェックを行うならAppSettingLoaderクラスで実施しようと思います。
:::

以下作成したテストソースとなります。
テストはEclipseで実行しやすく、処理分岐の網羅性も確認しやすいという点でJunitを採用しています。
※まあなんでも良いと思うんですが、実務でも使ってるんで使い慣れてるのにしました。

``` AppSettingFactoryTest.java
/**
 * 
 */
package app.config;

import static java.lang.System.*;
import static org.junit.Assert.*;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

/**
 * 
 */
public class AppSettingFactoryTest {

	//テストメソッド実行用変数
	String testlog;
	String testlogFile;
	String testTitle;
	String testwindUrl;
	int testwindWidth;
	int testwindHeight;

	/**
	 * @throws java.lang.Exception
	 */
	@Before
	public void setUp() throws Exception {
	}

	/**
	 * @throws java.lang.Exception
	 */
	@After
	public void tearDown() throws Exception {
	}

	@Test
	public void test1() {
		// パターン①：正常系　インスタンス生成が正常に生成でき、取得メソッドにて取得できる。
		String className = new Object() {
		}.getClass().getName();
		String resultOutput = className + "のテストパターン1";

		testlog = "テスト";
		testlogFile = "テスト";
		testTitle = "テスト";
		testwindUrl = "テスト";
		testwindWidth = 30;
		testwindHeight = 30;

		AppSettingFactory test1 = new AppSettingFactory();
		try {
			out.println("**********************************************");
			out.println(resultOutput + "が開始されました。");
			test1.createInstance(testlog, testlogFile, testTitle, testwindUrl, testwindWidth, testwindHeight);
			AppSettingDto testValue = test1.getInstance();

			assertEquals("テスト", testValue.getLogPass());
			assertEquals("テスト", testValue.getLogFileExtensionString());
			assertEquals("テスト", testValue.getTitle());
			assertEquals("テスト", testValue.getWindUrl());
			assertEquals(30, testValue.getWindWidth());
			assertEquals(30, testValue.getWindHeight());

			out.println("logPass: " + testValue.getLogPass());
			out.println("logFileExtension: " + testValue.getLogFileExtensionString());
			out.println("title: " + testValue.getTitle());
			out.println("windUrl: " + testValue.getWindUrl());
			out.println("windWidth: " + testValue.getWindWidth());
			out.println("windHeight: " + testValue.getWindHeight());

		} catch (Exception e) {
			String resultError = String.format("エラーが発生しました。内容は{%s}", e);
			out.println(resultError);
			e.printStackTrace();
			fail("テストパターン1失敗。");
		} finally {
			out.println(resultOutput + "が終了しました。");
			out.println("**********************************************");
		}
	}

	// パターン②：異常系　createInstanceの引数にNullが入っているときエラーとなる。
	@Test
	public void test2_1() {
		String className = new Object() {
		}.getClass().getName();
		String resultOutopu = className + "のテストパターン2_1";

		testlogFile = "テスト";
		testTitle = "テスト";
		testwindUrl = "テスト";
		testwindWidth = 30;
		testwindHeight = 30;

		AppSettingFactory test2_1 = new AppSettingFactory();
		try {
			out.println("**********************************************");
			out.println(resultOutopu + "が開始されました。");

			test2_1.createInstance(testlog, testlogFile, testTitle, testwindUrl, testwindWidth, testwindHeight);
			AppSettingDto testValue = test2_1.getInstance();

			assertEquals("テスト", testValue.getLogPass());
			assertEquals("テスト", testValue.getLogFileExtensionString());
			assertEquals("テスト", testValue.getTitle());
			assertEquals("テスト", testValue.getWindUrl());
			assertEquals(30, testValue.getWindWidth());
			assertEquals(30, testValue.getWindHeight());

			out.println("logPass: " + testValue.getLogPass());
			out.println("logFileExtension: " + testValue.getLogFileExtensionString());
			out.println("title: " + testValue.getTitle());
			out.println("windUrl: " + testValue.getWindUrl());
			out.println("windWidth: " + testValue.getWindWidth());
			out.println("windHeight: " + testValue.getWindHeight());

		} catch (Exception e) {
			// TODO: handle exception
			String resultError = String.format("エラーが発生しました。内容は{%s}", e);
			out.println(resultError);
			fail("テストパターン2-1失敗。");
		} finally {
			out.println(resultOutopu + "が終了しました。");
			out.println("**********************************************");
		}
	}

	// パターン②：異常系　createInstanceの引数に空白が入っているときエラーとなる。
	@Test
	public void test2_2() {
		String className = new Object() {
		}.getClass().getName();
		String resultOutopu = className + "のテストパターン2_2";

		testlog = "　　";
		testlogFile = "";
		testTitle = "テスト";
		testwindUrl = "テスト";
		testwindWidth = 30;
		testwindHeight = 30;
		AppSettingFactory test2_2 = new AppSettingFactory();

		try {
			out.println("**********************************************");
			out.println(resultOutopu + "が開始されました。");

			test2_2.createInstance(testlog, testlogFile, testTitle, testwindUrl, testwindWidth, testwindHeight);
			AppSettingDto testValue = test2_2.getInstance();

			assertEquals("テスト", testValue.getLogPass());
			assertEquals("テスト", testValue.getLogFileExtensionString());
			assertEquals("テスト", testValue.getTitle());
			assertEquals("テスト", testValue.getWindUrl());
			assertEquals(30, testValue.getWindWidth());
			assertEquals(30, testValue.getWindHeight());

			out.println("logPass: " + testValue.getLogPass());
			out.println("logFileExtension: " + testValue.getLogFileExtensionString());
			out.println("title: " + testValue.getTitle());
			out.println("windUrl: " + testValue.getWindUrl());
			out.println("windWidth: " + testValue.getWindWidth());
			out.println("windHeight: " + testValue.getWindHeight());

		} catch (Exception e) {
			// TODO: handle exception
			String resultError = String.format("エラーが発生しました。内容は{%s}", e);
			out.println(resultError);
			fail("テストパターン2-1失敗。");
		} finally {
			out.println(resultOutopu + "が終了しました。");
			out.println("**********************************************");
		}
	}

	// パターン②：異常系　createInstanceを実行せずに、getInstanceを実行してエラーとなる。
	@Test
	public void test3() {
		String className = new Object() {
		}.getClass().getName();
		String resultOutopu = className + "のテストパターン3";

		testlog = "テスト";
		testlogFile = "テスト";
		testTitle = "テスト";
		testwindUrl = "テスト";
		testwindWidth = 30;
		testwindHeight = 30;

		AppSettingFactory test3 = new AppSettingFactory();

		try {
			out.println("**********************************************");
			out.println(resultOutopu + "が開始されました。");
			AppSettingDto testValue = test3.getInstance();

			assertEquals("テスト", testValue.getLogPass());
			assertEquals("テスト", testValue.getLogFileExtensionString());
			assertEquals("テスト", testValue.getTitle());
			assertEquals("テスト", testValue.getWindUrl());
			assertEquals(30, testValue.getWindWidth());
			assertEquals(30, testValue.getWindHeight());

			out.println("logPass: " + testValue.getLogPass());
			out.println("logFileExtension: " + testValue.getLogFileExtensionString());
			out.println("title: " + testValue.getTitle());
			out.println("windUrl: " + testValue.getWindUrl());
			out.println("windWidth: " + testValue.getWindWidth());
			out.println("windHeight: " + testValue.getWindHeight());

		} catch (Exception e) {
			// TODO: handle exception
			String resultError = String.format("エラーが発生しました。内容は{%s}", e);
			out.println(resultError);
			fail("テストパターン2-1失敗。");
		} finally {
			out.println(resultOutopu + "が終了しました。");
			out.println("**********************************************");
		}
	}

}


``` 

テストパターンにある引数のNullおよび空白チェックですが、どのDtoでも同じことが必要になると思い、
共通関数として外だしすることとしました。

``` CommonFunction.java
public class CommonFunction {
	
	/************
	* メソッド名：Nullおよび空白チェック処理
	* 処理内容：可変長引数の中身をチェック。
	************/
	public static void checkNullBlank(String ...checkStrings) {
		int checkNum = checkStrings.length;
		
		for(int i = 0;i<checkNum; i++) {
			if(checkStrings[i]==null) {
				throw new IllegalArgumentException("項目のうち"+i+"番目がnullです");
			}
			if(checkStrings[i].isBlank()) {
				throw new IllegalArgumentException("項目のうち"+i+"番目が空白です");
			}
		}
	}
}
```

::: note info
上記の関数を考えましたが、実行してみて致命的な欠陥に気付きました。
上記の関数の場合、引数の中で最初にNULLまたは空白があったものを発見した時点で処理がストップしてしまいます。

そのため、以下のように修正しました。
引数を可変長引数にすることで、使用場面の引数の違いを意識しなくて良いようにしました。
:::

``` CommonFunction.java
public class CommonFunction {
	
	/************
	* メソッド名：Nullおよび空白チェック処理
	* 処理内容：可変長引数の中身をチェック。
	************/
	public static void checkNullBlank(String ...checkStrings) {
		int checkNum = checkStrings.length;
		StringBuilder errorMessage = new StringBuilder();
		
		for(int i = 0;i<checkNum; i++) {
			if(checkStrings[i]==null) {
				errorMessage.append("項目のうち").append(i).append("番目がnullです\n");
			}
			if(checkStrings[i].isBlank()) {
				errorMessage.append("項目のうち").append(i).append("番目が空白です\n");
			}
		}
		if(errorMessage.length()>0) {
			throw new IllegalArgumentException("入力チェックエラー: " + errorMessage.toString());
		}
	}
}
```
### 最後に
一旦今日はここまでです。
次回こそFunctionクラスのUTを開始したいと思っているのですが、共通関数をFuctoryクラスで使用するので、先にそっちのテストが必要となりました。
なんだか進めるたびに新しいタスクが湧き上がります😭
結局自分の設計不足なだけですね。
今日の振り返りとしては、テストソース作成すると考慮不足な点がどんどん見つかるので、テスト駆動開発という開発手法の有効性を少し実感しました。
とはいえ、結局テスト駆動の前提となる必要となるテスト結果の設計のスキルが必要なんですが。
ここまでお付き合いありがとうございました。