Spring BootでRDBやRedisを使うWebアプリのユニットテスト自動化について書く。とりあえず、H2DBとRedisをUnitテスト実行時に自動的に立ち上げるようにしてテストの自動化が出来るようにする。
WebアプリのUnitテスト
Webアプリを作るとき、MySQLやRedisなどのミドルウェアにデータを保存する。開発時にもMySQLやRedisが必要になる。当然、書いたプログラムのUnitテストを書くことになるが、これらのミドルウェアにデータが保存されるとなると開発時だけでなくUnitテストのときにも同等の役割をするミドルウェアが必要になる。また、DBの中に保存されているデータの管理もしなければならず意外とメンドウ。
次の項目があるとテストが楽。
- 環境の自動構築 (DBを立ち上げたり、停止したり)
- テストデータの用意 (DBへのmigrationを含む)
という、無理矢理な説明だけど、まぁテストとか開発する環境とかの構築では自動化は必須だしJavaだけで解決できると嬉しいよね。という話。
とりあえず、MySQLやRedisはTest時に自動的に立ち上げるようにして環境を意識せずUnitテストできるようにする。CIでの自動化は書きません。
環境
- Java 8
- CentOS 7
- Dependency
RDBは組み込みDBのh2を使い、Unitテスト実行時にflyway-coreでDDLを流し込む。テストデータはSQLで用意するのはメンドウなのでUnitテストのコードからjpaを使ってテストコードに記述する感じにしてみる。基本的にインメモリDBにしてテストが終わったら全てのデータを捨てる。
Redisはembedded-redisという良さげなものがあるのでそれを使う。他はSpring initializrでポチポチすると追加出来る。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.flywaydb</groupId> <artifactId>flyway-core</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>com.github.kstyrc</groupId> <artifactId>embedded-redis</artifactId> <version>0.6</version> <scope>test</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency>
|
テスト用の設定の作成
src/test/resources
以下に次の設定を用意する。
テスト用のプロファイルを作成する。ここで指定したファイル名のunitの部分がProfile名になる。profile毎に設定ファイルを用意しておけば設定ファイルの差し替えが簡単に出来る。
application-unit.yml
spring: datasource: driver-class-name: org.h2.Driver url: jdbc:h2:mem:test;MODE=MySQL;DB_CLOSE_ON_EXIT=FALSE username: sa password: flyway.schemas: PUBLIC
spring.redis: host: localhost port: 6379 database: 0
|
インメモリDBで設定してテスト終了後のデータは捨てる。DB_CLOSE_ON_EXIT=FALSEを指定しないとH2に怒られるので設定しとく。
flyway.schemasはflywayが期待するスキーマとH2のデフォルトのスキーマが異なるので設定しないと実行できない。
Unitテスト時はSentinelいらないと思うのでRedisServerの設定をすることにする。
Redisは以下の様に設定する。
@RunWith(SpringRunner.class) @SpringBootTest public class SpringBootAutomationUnitTestSampleApplicationTests {
private static RedisServer redisServer; @BeforeClass public static void startRedis() throws IOException { redisServer = new RedisServer(6379); redisServer.start(); } @AfterClass public static void stopRedis() { redisServer.stop(); } }
|
設定はコレくらい。だが、本物のRedisが動くのでLinuxで動かす場合は以下のカーネルパラメータを設定しないと動かなかった。
$ sysctl -w vm.overcommit_memory=1 $ sysctl -w net.core.somaxconn=1024
|
テストの準備
適当なDDLとEntityとControllerなどを用意する。
まず、DDLから。パスとファイル名は意味があるので、詳しくはflywayのドキュメントを読んでください。
src/test/resources/db/migration/V1__init.sql
create schema if not exists "public"; create table user ( id bigint generated by default as identity, name varchar(255) not null, email varchar(255) not null, password varchar(255) not null );
|
対応するEntity
@Data @NoArgsConstructor @AllArgsConstructor @Entity public class User { @Id @GeneratedValue private Long id;
@Column(nullable = false) private String name;
@Column(nullable = false) private String email;
@Column(nullable = false) private String password; }
|
適当なRepository。
@Repository public interface UserRepository extends JpaRepository<User,Long> { User findByName(String name); }
|
ココまで作ったら適当なテストをするためのコントローラを作る。
@Controller public class UserController {
@Autowired private UserRepository userRepository;
@RequestMapping(value = "/", method = RequestMethod.GET) @ResponseBody public String getUser() { return userRepository.findByName("alice").getEmail(); } }
|
テストを書いてみる
setUpでコントローラで取得するデータをDBに書き込む。
@RunWith(SpringRunner.class) @SpringBootTest(classes = SpringBootAutomationUnitTestSampleApplication.class) @AutoConfigureMockMvc public class UserControllerTest {
@Autowired private MockMvc mockMvc;
@Autowired private UserRepository userRepository;
private User testUser;
@Before public void setUp() { testUser = userRepository.saveAndFlush( new User(0L, "alice", "alice@example.com", "password")); }
@After public void tearDown() { userRepository.delete(testUser); }
@Test public void testGetUser() throws Exception { mockMvc.perform(MockMvcRequestBuilders.get("/")) .andExpect(MockMvcResultMatchers.status().is2xxSuccessful()) .andExpect(MockMvcResultMatchers.content() .string("alice@example.com")); } }
|
実行してみる。
$ ./mvnw clean test --define spring.profiles.active=unit
|
テストが正常に実行されることが確認できる。unitテストのときだけ実行したいテストケースは以下の様にアノテートするとprofileが一致するときだけテストが実行されるように出来る。
@IfProfileValue(name = "spring.profiles.active", values = "unit")
|
おまけ
Redisにつながっているか確認してみる。
@Autowired private StringRedisTemplate redisTemplate;
@Test public void redisTest() { redisTemplate.opsForValue().set("testKey", "testValue"); Assert.assertNotNull(redisTemplate.opsForValue().get("testKey")); Assert.assertEquals("testValue", redisTemplate.opsForValue().get("testKey")); }
|
おわり。
参考
- http://docs.spring.io/spring-boot/docs/current/reference/html/howto-database-initialization.html
- https://github.com/spring-projects/spring-boot/tree/v1.4.2.RELEASE/spring-boot-samples/spring-boot-sample-flyway