테스트를 자주 만드는 개발자들은 알겠지만, 테스트를 만드는 것은 그닥 어렵지 않다. 테스트를 잘 만드는 것이 어려울 뿐이다.

테스트를 만들고 코드를 작성하고, 테스트를 변경하고 코드를 변경하고....반복된 작업을 하다가 보면, 내가 무슨 짓을 하고 있는지 감을 잡기도 어렵고, 테스트를 매번 고쳐주는 것도 싫증이 난다.

자꾸 테스트가 깨지는 이유가 무엇일까?
그 중에 대표적인 경우가 테스트할 메소드 안에서 테스트된 메소드나 다른 메소들을 호출을 할때 새로운 메소드가 추가가 되면서 깨지는 경우가 많다. 그 경우 메소드 안에서 호출한 메소들이 모두 깨지게 되는데 이 현상을 해결하려면 하나의 로직을 변경했다고 하더라도 테스트를 통과하기 위해 여러개의 단위 테스트 메소드에 손이 가야 한다. (음..말이 좀 어렵군) 단위 테스트에 다수의 경험이 있으신 분들중에 이 고민 안해본적 없을 것이다.

난 3가지 경우로 문제를 해결을 한다.
1. delegate class
2. method overriding
3. 자신의 레퍼런스를 mock으로 치환

1번의 경우 작업들이 비교적 순차적이고, 응집성이 높은 작업들을 할때 extract class를 통해서 가독성도 좋아지고, host class의 dependency가 이뻐보이게 될떄는 1번을 사용한다. 이 방법이 가장 정석적인 방법이다.

근데, 애매한 메소드들이 있다. host와 dependency가 거의 같고, seperate를 하자니 클래스도 많아지고 웬지모를 찜찜한 기분이 들떄는 과감하게 2번을 사용한다.  약간의 노출수위(private->protected)가 거슬리기는 하지만, 견고한 테스트를 만든다면, 약간의 노출은 괜찮다는 생각이다.

3번의 경우는 클래스를 테스트할 때 특정메소드에 집중해서 테스트를 할때 사용이 된다. 특정 메소드를 중점으로 테스트를 하고 나머지 부분들은 단순 콜만 된다면, 만족스러운 테스트인 경우이다. 테스트할 메소드의 갯수에 따라서 서비스 코드를 잘 짜야 하는 경우이다.

3 번의 경우도 몇번 고려는 했었는데, 특이한 경우 안이면, 난 1,2번을 선호하는 편이다. 1번은 테스트의 정석인 방법이라 제일 먼저 고려를 해보는 방법이고, 2번은 mock을 만들지 않고 비교적 쉽게 테스트가 가능하다는 것이 장점이다.

3번의 경우를 고려해 볼수 있는 것은 거의 대부분 1번으로 처리를 하려고 하고, 1,2번 사이에서 메소들의 행위나 특성들이 애매할 경우는 범위를 고려해서 3번을 선택한다.

갑자기 이 글을 쓴 것은 아래 글을 읽다가 나도 비슷한 경우의 경험을 많이 해서 흔적으로 남기고 싶어서이다. 아래 내용을 읽고 위에 내용을 보면, 좀더 이해가 쉬울 것이다. EP처럼 구체적인 예로 적을 수도 있지만, 워낙 설명을 잘해놔서 아래 내용을 참고하는게 더 나을거라는 생각이다. 내가 제시한 3가지 사례중 주로 3번에 대한 내용을 구체적으로 잘 설명을 했다.
http://colus.egloos.com/4639268
Posted by ologist

아래 글을 보다가 내가 테스트를 관리하는 방법을 소개를 한다. 내가 Working Effectively with Legacy Code책을 읽고 나서 바로 실무에 적용을 한 내용중의 하나이다.

단위 테스트는 빨리 실행을 할 수 있어야 한다.
http://javajigi.tistory.com/97

일단 단위 테스트(TestCase)를 담을 suite를 2개를 만든다. 2개의 suite은 상징적인 갯수를 얘기할 수도 있다. 의미의 분류에 따라서 숫자가 늘어날수도 있다. 하지만, 너무 suite이 많아지는 것은 비추천...^^;

진정한 단위 테스트를 모아둘 suite를 하나 만들어서 그곳에 add를 하고, 다른 suite에는 integration에 대한 테스트를 넣어둔다. integration테스트는 DB 또는 HTTP 등등의 외부자원이 필요한 경우이다.

테스트 패키지의 root에는 다음과 같은 클래스가 존재를 한다.
* EP2AllTests
* EP2IntegrationAllTests
* EP3AllTests
* EP3IntegrationAllTests

2개의 분리된 테스트들은 테스트 수행 interval이 틀리다. 아무래도 integration이 아닌 진정한 단위테스트에 더 손이 많이 가게 되고, 빠르게 실행이 되기때문에 자주 하게 된다.

EP2AllTests를 실행은 빠르게 실행이 되기때문에 실행을 자주 할수가 있다. 외부환경에 대해서는 stub이나 mock으로 처리를 했기때문에 test fail이 발생을 하면 안된다.

EP2IntegrationAllTests를 실행하면, 시간이 좀 걸린다. 이 suite같은 경우는 109개 밖에 되지 않기때문에 12초정도면 수행이 가능하지만, 아마 계속 테스트가 증가를 하게 되면, 시간이 더 걸릴 수도 있을 것이다. 그리고, DB나 HTTP와 같은 외부연동부분을 테스트를 하는 것이라 깨질 확률도 더 높다.

2가지의 생각으로 분류를 한다. 빠르게 테스트가 될수가 있는 것, 테스트가 깨지기 쉬운 것을 따로 모아두면, 테스트를 실행할 때 좀더 편안하게 볼수가 있다. 테스트가 깨지기 쉬운 것은 dependency를 최대한 제거를 해서 테스트가 잘 안깨지도록 수정을 해야 한다.

TIP) suite를 모두 모아서 한번에 실행하는 suite을 만들 수도 있다...^^

DB와 파일의 선택의 기로에 서서
난 자주 바뀌지 않는 값들은 DB보다는 파일에 저장을 하는 것을 선호한다. 물론 자바코드내에 넣기도 한다. DB보다 파일을 선호하는 가장 큰 이유중의 하나는 테스트가 쉽다는 것이다. 간단하게 내용을 읽는 속도 퍼포먼스도 파일이 빠르다. DB는 컨넥션 자체가 큰 부담이 되기때문이다. 특히 우리가 사용하는 시스템은 DB연결이 너무 많아서 테스트할때마다 짜증이 난다.
 
DB는 영속성을 위한 훌륭한 솔루션이기는 하지만, 파일로 저장해서 큰 무리가 없는 것은 그냥 파일에 저장을 한다.

Posted by ologist

추상 클래스에 행위에 대해서 테스트가 필요하다면, 방법이 있다.  EP님이 블로그 덧글을 남긴 것을 보고 좀더 구체적으로 직접 물어봐서 좋은 가이드를 얻을 수가 있었다. 추상클래스 단위 테스트 만들기를  보고 이어서 보면 더 이해가 쉬울 것이다.

대부분 hook메소드와 같은 콜백 메소드가 부모의 알고리즘(비즈니스 로직)에 따라서 정상적으로 잘 호출이 되는지를 체크할 수 있는 테스트이다.

아이디어는 이렇다. EP님의 덧글을 참조하자.

여러 개의 메서드를 구현할 것을 기대하고 이를 정해진 순서대로 실행하는 추상 클래스를 테스트할 때에는 로그 찍는 방법도 좋습니다. 다만 로그를 사람이 눈으로 확인하는 것이 아니라 로그를 남기고 기대하는 로그를 단언하면 됩니다. - EP

구현은 이렇게 했다. 메소드가 호출되는 것을 Stack에 로그로 담아두고, 테스트를 원하는 메소드를 호출후에 원하는 메소드들이 호출이 되고, 원하는 상태로 변경이 되어있는지에 대한 테스트를 하는 것이다. 호출된 로그는 Stack에 담아서 pop을 하면서 비교를 하였다.

이정도의 테스트이면,
상태와 행위를 둘다 테스트를 해서 좀더 견고하고 원하는 테스트를 할수 있을 것이다.
public class AbstractBlogTemplateClientTest extends TestCase {
final Stack<String> methodStack = new Stack<String>();
 
 protected void setUp() throws Exception {
  super.setUp();
  leverageDAO = createMock(LeverageDAO.class);
  abstractBlogTemplateClient = new AbstractBlogTemplateClient(){
   @Override
   protected SourceType getSourceType() {
    methodStack.push("getSourceType");
    return SourceType.RECIPE;
   }
   @Override
   protected Leverage getLeverage(Post post) {
    getSourceType();
    methodStack.push("getLeverage");
    return leverage;
   }
   @Override
   protected void resultSyncCallback(Post post, RemoteResult remoteResult) throws Exception {
    methodStack.push("resultSyncCallback");
   }
   @Override
   protected RemoteResult insert(Post post) throws Exception {
    methodStack.push("insert");
    return remoteResult;
   }
   @Override
   protected RemoteResult update(Post post) throws Exception {
    methodStack.push("update");
    return remoteResult;
   }
   @Override
   protected RemoteResult delete(Post post) throws Exception {
    methodStack.push("delete");
    return remoteResult;
   }
  };
  abstractBlogTemplateClient.setLeverageDAO(leverageDAO);
 }
 @Override
 protected void tearDown() throws Exception {
  super.tearDown();
  methodStack.clear();
 }
 public void test_insertToOuter() throws Exception {
  leverageDAO.insertLeverage(leverage);
 
  replay(leverageDAO);
  assertTrue(post.getLeverageSet().size() == 0);
 
  abstractBlogTemplateClient.insertToOuter(post);
 
  verify(leverageDAO);
 
  // state test
  assertTrue(post.getTemplateSync());
  assertFalse(post.getTemplateCopyYn());
  assertNotNull(post.getTemplateSyncdate());
  assertTrue(post.getLeverageSet().size() == 1);
 
  // method call test : behavior
  assertEquals(4, methodStack.size());
  assertEquals("getLeverage", methodStack.pop());
  assertEquals("getSourceType", methodStack.pop());
  assertEquals("resultSyncCallback", methodStack.pop());
  assertEquals("insert", methodStack.pop());
 }
}

메소드가 호출되는 것을 체크할 뿐만 아니라 호출된 순서까지도 체크를 할수가 있다.


Posted by ologist
운영 및 유지보수라는 단어의 의미는 너무나 다양하고, 광범위하게 쓰인다

소프트웨어 유지보수의 중요성 을 보면서 다시 한번 유지보수에 대해서 생각을 하게 되었고, 테스트의 중요성에 대해서 생각을 해봤다.

시스템에 따라서 행위가 많이 달라지기 때문이다. 개발자들은 유지보수를 많이 해봐야한다. 유지보수를 해보지 않고, 신규 개발을 잘하기는 정말 어렵다. 시스템의 라이프싸이클이 개발하는 기간보다는 유지보수를 하는 기간이 훨씬 길다는 사실은 너무나 잘 아는 이야기이다.

나는 하나의 시스템을 가지고,  4년동안 유지보수를 해본 경험이 있다. 앞으로도 쉽게 가질 수 있는 소중한 경험이었다. 그 경험은 나에게 큰 도움이 되고 있다. 유지보수를 오래할 수록 안정적이게 된다. 물론, 그 도메인 영역에 대한 이해도가 높아져서이기도 하지만, 자주 있는 일에 대한 테스트를 하나씩 만들어서 추가를 하기때문이기도 하다.

제일 중요한 포인트는 어떤 방식으로던지 테스트가 가능해야 하다는 것이다. 테스트를 1초라도 빨리할 수 있으면 유지보수시에 잦은 변경사항을 적은 노력으로 버그를 최소화하면서 변경을 할수가 있다. 초보시절에는(junit을 사용하기전에는) 주로  웹서버에서
굵게주로 테스트를 하는 mock페이지를 많이 만들었는데, 몇 년전부터 지금까지는 junit을 이용해서 단위 테스트를 가능한 많이 만들고 있다.

단위 테스트만큼 빠르게 테스트가 가능한 것은 없을 것이다.
난 더욱더 테스트 중독에 빠지려고 한다.





Posted by ologist
이전버튼 1 이전버튼

블로그 이미지
ologist

공지사항

Yesterday171
Today56
Total34,799

달력

 « |  » 2012.02
      1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29      

최근에 받은 트랙백

글 보관함