テスト

Spring Shell は、シェルアプリケーションのテストを容易にするためのユーティリティをいくつか提供しています。これらのユーティリティは、ユーザー入力のシミュレーション、出力のキャプチャー、制御された環境でのコマンド動作の検証に役立ちます。

テストアサーション

Spring Shell は、コマンドの実行と出力を検証するための次のテストアサーション API を提供します。

  • ShellScreen: このクラスはシェル画面を表し、ユーザーに表示される出力をキャプチャーして分析することができます。

  • ShellAssertions: このクラスは、シェルコマンド実行の結果をアサートするための静的メソッドを提供します。

  • ShellTestClient: このクラスを使用すると、ユーザー入力をシミュレートし、シェルコマンドをプログラムで実行できます。

これらの API をテストで使用する方法の例を次に示します。

@ExtendWith(SpringExtension.class)
class ShellTestClientTests {

	@Test
	void testCommandExecution(@Autowired ShellTestClient shellTestClient) throws Exception {
		// when
		ShellScreen shellScreen = shellTestClient.sendCommand("test");

		// then
		ShellAssertions.assertThat(shellScreen).containsText("Test command executed");
	}
}

テストアノテーション

Spring Shell は、テストクラスが Spring Shell テストであることを示すために用いられる @ShellTest アノテーションを提供します。これは、シェルコマンドのテストに必要なコンテキストを設定します。このアノテーションは spring-shell-test-autoconfigure モジュールで定義されており、Spring Boot アプリケーションで使用するように設計されています。

Spring Boot アプリケーションクラスを定義したら、@ShellTest アノテーションを付与したテストクラスを作成して、シェルコマンドをテストできます。以下に例を示します。

@SpringBootApplication
public class ExampleShellApplication {

	@Command(name = "hi", description = "Says hello")
	public String hello() {
		return "hello";
	}

}

@ShellTest
@ContextConfiguration(classes = ExampleShellApplication.class)
class ShellTestIntegrationTests {

	@Test
	void testCommandExecution(@Autowired ShellTestClient client) throws Exception {
		// when
		ShellScreen shellScreen = client.sendCommand("hi");

		// then
		ShellAssertions.assertThat(shellScreen).containsText("hello");
	}

	@Test
	void testUnknownCommandExecution(@Autowired ShellTestClient client) {
		Assertions.assertThatThrownBy(() -> client.sendCommand("foo"))
                  .isInstanceOf(CommandNotFoundException.class);
	}

}

Spring Shell は、テスト目的でカスタム入力を提供するのに使用できる ShellInputProvider API も提供しています。これにより、ユーザー入力(平文とパスワード)をより柔軟にシミュレートできます。

例: コマンドが次のような入力を期待する場合:

@Bean
public Command ask() {
    return new AbstractCommand("ask", "Ask for user input") {
        @Override
        public ExitStatus doExecute(CommandContext commandContext) throws Exception {
            String message = commandContext.inputReader().readInput();
            commandContext.outputWriter().println("You said: " + message);
            return ExitStatus.OK;
        }
    };
}

その後、ShellInputProvider を使用してテスト中に必要な入力を供給することで、ユーザーの入力をシミュレートできます。

@Test
void testCommandExecutionWithReadingInput(@Autowired ShellTestClient client) throws Exception {
    // given
    ShellInputProvider inputProvider = ShellInputProvider.providerFor("ask").withInput("hi").build();

    // when
    ShellScreen screen = client.sendCommand(inputProvider);

    // then
    ShellAssertions.assertThat(screen).containsText("You said: hi");
}

エンドツーエンドテスト

Spring Shell のテスト機能を使用せずにシェルアプリケーションのエンドツーエンド(つまりブラックボックス)テストを行う場合は、Spring Boot が提供するテストユーティリティを使用できます。Spring Boot アプリケーションクラスを定義したら、@SpringBootTest アノテーションを付与したテストクラスを作成してシェルコマンドをテストできます。以下に例を示します。

@SpringBootApplication
public class MyShellApplication {

	public static void main(String[] args) {
		SpringApplication.run(MyShellApplication.class, args);
	}

	@Command
	public void hi() {
		System.out.println("Hello world!");
	}

}

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE,
		useMainMethod = SpringBootTest.UseMainMethod.ALWAYS,
		classes = { MyShellApplication.class },
		properties = { "spring.shell.interactive.enabled=false" },
        args = "hi")
@ExtendWith(OutputCaptureExtension.class)
public class ShellApplicationEndToEndTests {

	@Test
	void testCommandOutput(CapturedOutput output) {
		assertThat(output).contains("Hello world!");
	}

}

この例では、テストコンテキストで Spring Shell アプリケーションを実行し、Spring Boot の OutputCaptureExtension を使用してコマンドの出力を確認する方法を示します。

この方法の注意点は、コマンドが @SpringBootTest アノテーションの args プロパティを介して渡されるため、同じテストクラスで複数のコマンドをテストするのに便利ではないことです。複数のコマンドをテストするには、前のセクションで説明した Spring Shell テストユーティリティを使用することをお勧めします。以下に、Spring Shell テストユーティリティを使用して複数のコマンドをテストする方法の例を示します。

@SpringBootApplication
public class GreetingShellApplication {

	public static void main(String[] args) {
		SpringApplication.run(GreetingShellApplication.class, args);
	}

	@Command
	public void hi(CommandContext context) {
		context.outputWriter().println("Hello world!");
	}

	@Command
	public void bye(CommandContext context) {
		context.outputWriter().println("Goodbye world!");
	}

}

@ShellTest
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE,
		useMainMethod = SpringBootTest.UseMainMethod.ALWAYS, classes = { GreetingShellApplication.class },
		properties = { "spring.shell.interactive.enabled=false" })
public class ShellApplicationEndToEndMultipleCommandsTests {

	@Test
	void testCommandExecution(@Autowired ShellTestClient client) throws Exception {
		// when
		ShellScreen shellScreen = client.sendCommand("help");

		// then
		ShellAssertions.assertThat(shellScreen).containsText("AVAILABLE COMMANDS");
	}

	@Test
	void testHiCommandExecution(@Autowired ShellTestClient client) throws Exception {
		// when
		ShellScreen shellScreen = client.sendCommand("hi");

		// then
		ShellAssertions.assertThat(shellScreen).containsText("Hello world!");
	}

	@Test
	void testByeCommandExecution(@Autowired ShellTestClient client) throws Exception {
		// when
		ShellScreen shellScreen = client.sendCommand("bye");

		// then
		ShellAssertions.assertThat(shellScreen).containsText("Goodbye world!");
	}

}