Integration Testing in a Spring Project

2016. 1. 19. 05:07Java/Spring Framework

하루에 하나씩 ...
새벽에 잠도 깨고... 암튼...
오늘은 자그마치 2년여년에 올라온 글을 번역 하도록 하겠습니다.
주요 요지는 그렇습니다.
DB 연동 테스트를 하는데 있어서 , 테스트를 종료 하고 RollBack 하는 문제가 있습니다.
하지만 MockMVC 상에 Transaction을 걸어주면, 스프링 테스트 프레임워크가 테스트가 끝나고 나면, 자동으로 롤백을 시켜준다라는 내용입니다.







우리는 Privotal Labs에서의 테스팅을 사랑합니다.
(참고로, 번역중에 우리라는 것은 사실 pivotal 회사를 지칭하는 것입니다.)
모든 프로젝트중 일부 포인트에서 모든 쌍(pair)은 "그래서 어떻게 우리는 이걸 테스트 할 꺼야?"  라는 질문을 묻습니다.
(Pair programming을 하나 봅니다... 애자일 스럽게 말이죠)
우리 테스팅 전략이 데이터 레이어에 접근하는 것을 포함한다면, 테스트 하기 이전에 데이터 단계를 아는 것이 중요하죠.
In rspec, 이건은 가끔씩 하나의 데이터베이스 트랜젝션 주변에 각각의 테스트를 배치하는 것으로 끝납니다.
혹시 가능하지 않다면, (아마도 테스트 코드는 DB connection에 대한 접근을 갖지 않습니다.)
그땐 데이터베이스 클리닝 전락이 가끔 사용됩니다.
이 전략은 종종 테스트들 간에 데이터베이스 테이블을 생략(truncate)합니다.
여기 Spring world에서 매우 잘 사용된 통합 테스팅 기술들을 있습니다.
첫번째는 트랜젝션을 사용하였고, 두번째는 데이터 클린을 사용했습니다.

Spring MVC Test Framework

첫번째는 Spring MVC test Framework 입니다.
이 Test Framework는 Servlet Container의 동작을 요구하지 않습니다.
이것은 전혀 표현(rendering)되지 않는 JSP들로부터 보는 것을 의미합니다.
(해석이 이상하네요... 이겁니다. This means that view from JSPs are not rendered at all.)

Velocity와 같은 다른 렌더링은 view들을  표현(render)할것입니다.
여기서 하나의 유용한 기능은 어노테이션들을 가진 트랜젝션들에게 개인적인 테스트들을 배치할수 있는 능력입니다.
여기 하나의 트렌젝션 상에서 배치된 간단한 GET Request 테스트 예제가 있습니다.
이 테스트들은 Rails world에서의 Controller tests과 비교 할수 있습니다.

@WebAppConfiguration
@ContextConfiguration("file:src/main/webapp/WEB-INF/mvc-dispatcher-servlet.xml")
@TransactionConfiguration(transactionManager="transactionManager" , defaultRollback=true)
@Transactional
public class AppTest{
     private MockMvc mockMvc;
     @SuppressWarnings("SpringJavaAutowiringInspection")
     @Autowired
     protected WebApplicationContext wac;

     @Before
     public void setup(){
          this.mockMvc = webAppContextSetup(this.wac).build();
     }
     
     @Test
     public void simple() throws Exception{
          mockMvc.perform(get("/"))
                       .andExpect(status().isOk());
     }
}


@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration@TransactionConfiguration(transactionManager="txMgr", defaultRollback=false)
@Transactional
public class FictitiousTransactionalTest {

    @BeforeTransaction
    public void verifyInitialDatabaseState() {
        // logic to verify the initial state before a transaction is started
    }

    @Before
    public void setUpTestDataWithinTransaction() {
        // set up test data within the transaction
    }

    @Test
    // overrides the class-level defaultRollback setting
    @Rollback(true)
    public void modifyDatabaseWithinTransaction() {
        // logic which uses the test data and modifies database state
    }

    @After
    public void tearDownWithinTransaction() {
        // execute "tear down" logic within the transaction
    }

    @AfterTransaction
    public void verifyFinalDatabaseState() {
        // logic to verify the final state after transaction has rolled back
    }

}

HtmlUnit
이건, 브라우저 테스트 말고, 프로그램 단위에서 테스트 해보도로고 도와 주는 것이죠

우선 번역 전에, HtmlUnit 에 대한 곳에서 정의 부분만 가져왔습니다. 이건 영문으로 읽어야 됨.
HtmlUnit is a "GUI-Less browser for Java programs".
It models HTML documects and provides an API that allows you to invoke pages, fill out forms, click links , etc...
just like you do in your "normal" browser.

It has fairly good JavaScript support (which is constantly improving) and is able to work even with quite complex AJAX libraries, simulating Chrome, Firefox or Intenet Explorer depending on the configuration used.

It is typically used for testing purposes or to retrieve information from web sites.

HtmlUnit is not a generic unit teting framework.
It is specifically a way to simulate a browser for testing purposes and is intended to be used within another testing framework such as Junit or TestNG.


HtmlUnit은 전체 어플리케이션 서버의 동작과 동작하는 서버에 대한 테스트들의 실행을 포함하고 있습니다.
이것은 views이 나타나지고, 이들의 컨텐츠들이 확인 할 수 있다는 것을 의미 합니다.
이 테스트들은 cabybara를 사용하여 작성된 우리의 rspect 통합 테스트과 매우 비슷합니다.
이 테스트는 form상에 몇몇의 값들을 채우고, 그것을 submit 하고 입력된 form 데이터와 업데이트된 페이지를 체크합니다.
이미 동작중인 어플리케이션 서버에 대하여 그 테스트들이 동작함으로써,
우리는 하나의 트랜잭션상에 테스트를 표현할 수 없습니다.

우리는 데이터베이스 이전(migrations)을 위해 flyway를 사용하고 있으므로,이것은 우리에게 @FlywayTest 어노테이션을 우리 테스트에서 하나의 clean DB에 대해 각각의 테스트를 동작하기 위해 사용하도록 하고 있습니다. 

rails 테스팅 세계에서 데이터베이스 클리너와 같이,
DB는 정리(cleaned)되고, 각각의 테스트 후에 재 생성(re-created)되어 집니다.

동일한 Spring context configuration은  우리가 테스트들을 실행하는application server로써 사용되어 집니다.
테스트 클래스에서 @FlywayTest 어노테이션을 기억해주세요.
우리는 flyway-test-extenstions로 부터 spring4 branch를 이용해야만 했습니다.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("file:src/main/webapp/WEB-INF/mvc-dispatcher-servlet.xml")
@TestExecutionListeners({DependencyInjectionTestExecutionListener.class, FlywayTestExecutionListener.class})
@FlywayTest
public class FormTest{
     @Test
     public void postForm() throws Exception{
          final WebClient webClient = new WebClient();
          final HtmlPage page = webClient.getPaget("http://localhost:3000/");
          Assert.assertTrue(page.asText().contains("Users"));
          final HtmlForm form = page.getFormByName("user");
          final HtmlSubmitInput button = form.getInputByValue("Add User");
          firstName.setValueAttribute("Jim");
          final HtmlTextInput lastName = form.getInputByName("lastName");
          lastName.setValueAttribute("Smith");
          final HtmlTextInput email = form.getInputByName("email");
          email.setValueAttribute("jim@smith.com");
          final HtmlPage page2 = button.click();
          
          Assert.assertTrue(page2.asText().contains("Smith, Jimtjim@smith.com"));
     }
}

모든 테스트 후에 마이그레이션들의 재 동작 배열(tactic)은 확실히 좀더 많은 마이그레이션들이 추가되어짐에 따라 실행 속도를 느리게 하게 합니다.
정기적으로 마이그레이션들을 압축하는 것은 도움이 될 것입니다.


아... 졸립다.. 자야지..