Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Spring と TDD

4,219 views

Published on

Spring Fest 2017
株式会社タグバンガーズ
小川岳史・古家優
https://youtu.be/GtUcZgTuDzU

Published in: Engineering
  • Be the first to comment

Spring と TDD

  1. 1. Spring と TDD Spring Fest 2017 2017.11.24 株式会社タグバンガーズ 小川岳史・古家優
  2. 2. 2 古家優 小川岳史 自己紹介 Spring Lover (10 years) Spring I/O, Spring One 参加 株式会社タグバンガーズ JSUG スタッフ インターン Springビギナー
  3. 3. 3 • TDDについて • Spring TestContext Framework • Spring Boot のテストサポート • Web のテスト • Security のテスト • HTML のテスト • RestClient のテスト • DB のテスト • Docker • まとめ 目次
  4. 4. 4 TDDについて
  5. 5. 5 TDDとは テストを書く 実装したい オブジェクト テストが成功する 最低限の実装 テスト 失敗 リファクタリング テスト 成功
  6. 6. 6 •Unit Tests •Integration Test •System Tests •User Acceptance Tests テストの種類
  7. 7. 7 Unit Test 対象のBeanを取り出してテストを行う(DIコンテナ使わない) Bean Bean テスト対象 Mock Mock @RunWith(JUnitMockitoRunner.class) public class ArticleManagerTest extends SampleBaseTestCase { @Mock private ArticleCalculator calculator; @Mock(name = "database") private ArticleDatabase dbMock; @Spy private UserProvider userProvider = new ConsumerUserProvider(); @InjectMocks private ArticleManager manager; @Test public void shouldDoSomething() { manager.initiateArticle(); verify(database).addListener(any(ArticleListener.class)); } } 依存依存
  8. 8. 8 Integration Test Tomcat API Browser DB cache テスト対象 TDDにおいてはインテグレーションテストが難しい 先にテストを作るが故に、連携先の対象が存在しない状態になってしまう IoC Container
  9. 9. 9 @Test @Before @After @AfterClass @BeforeClass @Ignore @Runwith @Category @Rule @SpyBeans @SpyBean @MockBean @MockBeans @TestConfiguration @TestComponent @SpringBootTest @DataMongoTest @AutoConfigureWebMvc @AutoConfigureRestDocs @AutoConfigureWebFlux @AutoConfigureTestEntityManager @AutoConfigureWebClient @TypeExcludeFilters @JooqTest @PropertyMapping @AutoConfigureJdbc @AutoConfigureTestDatabase @RestClientTest @WebMvcTest @DataNeo4jTest @AutoConfigureWebTestClient @AutoConfigureMockRestService Server @AutoConfigureDataJpa @DataRedisTest @DataJpaTest @AutoConfigureDataRedis @AutoConfigureJson @JsonTest @AutoConfigureMockMvc @AutoConfigureDataMongo @AutoConfigureJooq @AutoConfigureJsonTesters @JdbcTest @WebFluxTest @AutoConfigureDataNeo4j @DataLdapTest @AutoConfigureDataLdap @AutoConfigureCache @OverrideAutoConfiguration @SecurityTestExecutionListeners @WithAnonymousUser.java @WithMockUser.java @WithSecurityContext.java @WithUserDetails.java アノテーション @BootstrapWith @ContextConfiguration @WebAppConfiguration @ContextHierarchy @ActiveProfiles @TestPropertySource @DirtiesContext @TestExecutionListeners @Commit @Rollback @BeforeTransaction @AfterTransaction @Sql @SqlConfig @SqlGroup @IfProfileValue @ProfileValueSourceConfiguration @Timed @Repeat JUnit SpringFramework Spring Boot Spring Security
  10. 10. 10 Spring TestContext Framework IoC Container App Tomcat Browser DB cache API
  11. 11. 11 • Bean 同士の連携テストをするために必要 • 立ち上げた IoC コンテナから テスト対象の Bean をテストコードに対して DI したい • 実行速度が遅くなるため IoC コンテナはテストごとに 立ち上げたくない • テストによっては IoC コンテナ内の Bean を変更したい時がある テストと IoC コンテナ
  12. 12. 12 Spring TestContext Framework TestContextManager IoC Container アプリケーションのBean TestContextを立ち上げ管 理する テストするよ! Bean XXX を使う よ! 必要なBeanをTestContext経由でIoC コン テナからテストコードにDIする TestExecutionListener Ref Ref TestContext IoCコンテナを立ち上げる テストコードと対応するIoCコン テナをセットにして管理 テストするって言ってるから、 Bean XXXをくださいー テストコード @Test xxx xxxxxx @Autowired Xxx xxxxx コンテナは他のテストの時も 同じ Bean を利用する
  13. 13. @RunWith(SpringRunner.class) @ContextConfiguration("repository-config.xml") public class TitleRepositoryTests { @Autowired private TitleRepository titleRepository; @Test public void findById() { Title title = titleRepository.findById(10); assertNotNull(title); } } Spring TestContext Framework 13 ApplicationContext (IoC Container) TestContextManager TestContext DI 指定がない場合はTestクラスのパッケージ配下から 定義ファイルを検索してくれる
  14. 14. 14 Spring Boot のテストサポート IoC Container App Tomcat Browser DB cache API
  15. 15. 15 • 使いはじめるのが簡単 • AutoConfiguration による Bean の自動登録 • スライステスト • Bean定義のテスト Spring Boot のテストサポートのポイント
  16. 16. 16 https://start.spring.io/ で「Generate Project」するだけ Spring Initializr を使ってすぐに始められる
  17. 17. 17 Spring Initializr で用意してくれるライブラリ群 <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> test JSONPath テストでよく使うSpringテスト 3rd partyのライブラリ依存性を 初めから用意してくれている ダウンロードしたファイルのpom.xml は以下のテスト用の依存性が記載されている test
  18. 18. @RunWith(SpringRunner.class) @SpringBootTest public class TitleRepositoryTests { @Autowired private TitleRepository titleRepository; @MockBean private …; @Test public void findById() { Title title = titleRepository.findById(10); assertNotNull(title); } } @SpringBootTest 18 ApplicationContext (IoC Container) TestContextManager TestContext AutoConfigure 自動でテストに便利な Beanを定義 sourceフォルダ配下に自 分で定義したBean コンテナ内のBeanをモックに 差し替えてくれる Bootが用意してくれたよ しなにBean Tomcat立ち上げ
  19. 19. 19 スライステスト @WebMvcTest @DataJpaTest @RestClientTest @DataRedisTest @JsonTest @DataLdapTest 余計なBeanを作らない テスト対象の連携に必要なBeanのみ作成
  20. 20. 20 Bean 定義のテスト public class SecurityConfigTest { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner(); @Test public void test() { contextRunner.withUserConfiguration(SecurityConfig.class).run(context -> { Assertions.assertThat(context).hasNotFailed(); Assertions.assertThat(context).hasBean("springSecurityFilterChain"); }); } } 2.0.0.M6 ? Beanが通常通り作成できたか コンテナにBeanが存在するか IoC Container
  21. 21. 21 Web のテスト IoC Container App Tomcat Browser DB cache API
  22. 22. 22 @WebMvcTest IoC Container (WebApplicationContext)TomcatBrowser Dispatcher Servlet Handler Mapping/hello My Controller MockServletContext mvcValidatorMockMvc sourceフォルダ配下に自 分で定義したBean 余計なBeanを作らない スライス (@WebMvcTest) WebMvc用に便利な よしなにBean BrowserとTomcatの 代替をしてくれる
  23. 23. 23 MockMvc @RunWith(SpringRunner.class) @WebMvcTest public class WebLayerTest { @Autowired private MockMvc mockMvc; @Test public void shouldReturnDefaultMessage() throws Exception { this.mockMvc.perform(get("/")) .andDo(print()) .andExpect(status().isOk()) .andExpect(content().string(containsString("Hello World"))); } }
  24. 24. 24 MockHttpServletRequest: HTTP Method = GET Request URI = /hello Parameters = {} Headers = {Accept=[text/html]} Body = <no character encoding set> Session Attrs = {} Handler: Type = com.example.demo.HelloController Method = public java.lang.String com.example.demo.HelloController.hello() Async: Async started = false Async result = null Resolved Exception: Type = null ModelAndView: View name = hello View = null Model = null FlashMap: Attributes = null MockHttpServletResponse: Status = 200 Error message = null Headers = {Accept-Language=[en], Content- Type=[text/html;charset=UTF-8]} Content type = text/html;charset=UTF-8 Body = <html> <body> Hello </body> </html> Forwarded URL = null Redirected URL = null Cookies = []
  25. 25. 25 Security のテスト IoC Container App Tomcat Browser DB cache API
  26. 26. 26 Security 用の記述 <dependencies> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-test</artifactId> <scope>test</scope> </dependency> </dependencies>
  27. 27. 27 MockMvcとの相性が良い IoC Container Dispatcher Servlet Handler Mapping My Controller mvcValidator MockMvc @SecurityTestExecutionListeners @WithAnonymousUser.java @WithMockUser.java @WithSecurityContext.java @WithUserDetails.java filterや securityConfigを あらかじめ仕込ん でおける
  28. 28. 28 mvc.perform(post("/").with(csrf())) mvc.perform(get("/admin") .with(user("admin").password("pass") .roles("USER","ADMIN"))) @Test @WithMockUser public void requestProtectedUrlWithUser() throws Exception { mvc.perform(get("/")) ... } mvc.perform(formLogin("/auth").user("admin").password("pass")) 認証リクエスト
  29. 29. 29 mvc.perform(formLogin().password(“invalid”)) .andExpect(unauthenticated()); mvc.perform(formLogin() .user(“admin”)) .andExpect(authenticated() .withRoles("USER","ADMIN")); レスポンスの検証
  30. 30. 30 HTML のテスト IoC Container App Tomcat Browser DB cache API
  31. 31. 31 HTML のテスト 31 App Tomcat Browser Controller HtmlUnit GUI-less Browser ❌ Selenium 操作 クリックや入力など MockMvc WebDriver MockMvc を介して E2E テストができる JSも実行可能
  32. 32. 32 @RunWith(SpringRunner.class) @WebMvcTest public class LoginHtmlTests { @Autowired private WebDriver webDriver; @Test public void test() { webDriver.get("http://localhost:8080/hello"); Assertions.assertThat(webDriver.getCurrentUrl()) .isEqualTo("http://localhost:8080/login"); LoginPage loginPage = PageFactory.initElements(webDriver, LoginPage.class); loginPage.getUsername().sendKeys("user"); loginPage.getPassword().sendKeys("password"); loginPage.getSubmit().click(); Assertions.assertThat(webDriver.getCurrentUrl()) .isEqualTo("http://localhost:8080/hello"); } } WebDriver
  33. 33. 33 Page Objects public class LoginPage { @FindBy(css = "input[name=username]") private WebElement username; @FindBy(css = "input[name=password]") private WebElement password; @FindBy(css = "input[type=submit]") private WebElement submit; … }
  34. 34. 34 REST Client のテスト IoC Container App Tomcat Browser DB cache API
  35. 35. 35 @RestClientTest App Service RestTemplate MockRestServiceServer 代わり API ❌
  36. 36. 36 クライアントサイド @Service public class DetailsService { private final RestTemplate restTemplate; public DetailsService(RestTemplateBuilder restTemplateBuilder) { restTemplate = restTemplateBuilder.build(); } public Details getUserDetails(String name) { return restTemplate.getForObject("/{name}/details",Details.class, name); } }
  37. 37. 37 @RunWith(SpringRunner.class) @RestClientTest(DetailsService.class) public class DetailsServiceTest { @Autowired private DetailsServiceClient client; @Autowired private MockRestServiceServer server; @Autowired private ObjectMapper objectMapper; @Before public void setUp() throws Exception { String details = objectMapper.writeValueAsString(new Details("John Smith", "john")); server.expect(requestTo("/john")).andRespond(withSuccess(details, MediaType.APPLICATION_JSON)); } @Test public void whenCallingGetUserDetails_thenClientMakesCorrectCall() throws Exception { Details details = this.client.getUserDetails("john"); assertThat(details.getLogin()).isEqualTo("john"); assertThat(details.getName()).isEqualTo("John Smith"); } } テストコード
  38. 38. 38 DB のテスト IoC Container App Tomcat Browser DB cache API
  39. 39. 39 @DataJpaTest IoC Container TestEntityManager DataSource 余計なBeanを作らない スライス (@DataJpaTest) Data JPAに便利な よしなBean Internal DBと差し替えてく れる DB MyRepository @AutoConfigureTestDatabase イニシャルデータのセットアップ
  40. 40. 40 src/test/resources の application.properties に記載 イニシャルデータのセットアップ spring.datasource.schema=schema.sql spring.datasource.data=data.sql
  41. 41. 41 Docker のテスト IoC Container App Tomcat Browser DB cache API
  42. 42. 42 docker-compose.yml version: '2.3' services: mysql: image: mysql:5.6 ports: - "3306:3306" redis: image: redis:3.2.4 command: redis-server --appendonly yes --requirepass 1qazxsw2 ports: - "6379:6379" smtp: image: djfarrelly/maildev ports: - "25:25"
  43. 43. 43 fabric8io/docker-maven-plugin を使う pom.xml <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-failsafe-plugin</artifactId> <executions> <execution> <goals> <goal>integration-test</goal> <goal>verify</goal> </goals> </execution> </executions> </plugin> <plugin> <groupId>io.fabric8</groupId> <artifactId>docker-maven-plugin</artifactId> <version>0.23.0</version> <configuration> <images> <image> <external> <type>compose</type> <basedir>./</basedir> </external> </image> </images> </configuration> <executions> … </executions> </plugin>
  44. 44. 44 やっぱり Spring は TDD においても Bootiful だった! JOSH LONG さんによるこちらもデモがおすすめ Spring Tips: Bootiful Testing https://spring.io/blog/2017/11/22/spring-tips-bootiful-testing さいごに
  45. 45. Thank you!

×