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

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

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

난 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
What is Infinitest?

Infinitest is a continuous JUnit test runner designed to facilitate Test Driven Development. It
helps you get the most out of TDD by reducing your feedback cycle from minutes to seconds.

Infinitest 는 테스트기반의 개발을 하기 위해서 를 용이하게 하기 위해서 지속적으로 JUnit test를 실행하게 설계 되었다. 수분에서 수초까지 피드백 주기를 줄여주는 TDD로 부터 가장 중요한 것을 얻을수 있도록 해준다.

Using Infinitest

When Infinitest first launches, it runs any test that extends from
junit.framework.TestCase or has methods that have the org.junit.Test
annotation. After that, Infinitest will run the test again if it, or one of it's dependencies, changes.
Changes to direct and indirect dependencies will trigger a test run. Only the minimal set of tests
which depend on those changed files will be run.

첫번째로 Infinitest 어플리케이션이 실행될때 junit.framework.TestCase를 상속한 클래스나 org.junit.Test annotation을 찾아서 모두 실행을 한다. 그 이후에는 테스트 또는 그것에 의존하는 것들이 변경되면 테스트를 다시 실행을 한다. 직접,간접적으로 의존하는 것들의 변경되는 경우 테스트를 자동으로 실행을 한다.  변경된 파일에 의존하는 테스트들의 최소한의 셋만 실행이 될 것이다.

Test Driven Development
Infinitest is made with the Test Driven Developer in mind. You'll find yourself keeping your
hands on the keyboard, in the flow, as Infinitest runs your tests for you with each change.
Infinitest will act as a virtual pair programmer...keeping your honest and helping you take small
steps.

Refactoring
Refactoring is easy with Infinitest. The bar should always stay green. If the bar turns red, you
made a mistake! Back up and try again.
Refreshing The Graph
If you find yourself needing to run all of your tests from Infinitest, you can rebuild the
dependency graph by hitting the reload button. This will rebuild the dependency graph and
re-run all of your tests.

Stopping the tests
If you need to temporarily stop Infinitest from running your tests, you can hit the stop
button. This will prevent Infinitest from running tests after changes occur. When you resume
running tests (by hitting the button again), the entire test suite will be run.

Configuring Infinitest

Filtering Tests

You may have tests in your classpath that you don't want Infinitest to run. These tests can be
filtered out by creating a file in the working directory of your project named
infinitest.filters. It should contain one regular expression per line. Any class names
(not file names) that match any regular expressions in that file will not be run. For example:
org\.myproject\.acceptance\..*
.*\$.*
will filter out all the classes in the com.myproject.acceptance package, and any inner
classes (which always contain a $).

ex) 내가 진행하는 프로젝트인 경우는 서버환경에 따라서 테스트가 실패할수 있는 테스트는 IntegrationTest를 suffix에 붙여서 사용한다. 그래서 다음과 같이 파일을 만들어서 넣어주면 Infinitest 어플리케이션이 알아서 exclude를 하고 실행을 한다.

* 파일 이름 : infinitest.filters
* 파일 내용
com\.nhn\.community\..*IgnoreTest
com\.nhn\.community\..*IntegrationTest
com\.naver\.blog\..*IgnoreTest
com\.naver\.blog\..*IntegrationTest
.*\$.*

간단후기~

모든 테스트가 실행될때 보이는 하단에 progressive bar가 중독성있네요.

Infinitest 사용을 해보니 편하고 재미도 있습니다. 코드 개발을 하다가 보면, 테스트 돌리는 것도 귀찬은 작업중의 하나인데, 자동(trigger)으로 해주니 좋습니다요!
jsp파일에 너무 많은 코드 들어가서 다음과 같은 에러가 발생을 했다.

* 에러로그
An error occurred at line: 70 in the generated java file
The code of method _jspService(HttpServletRequest, HttpServletResponse) is exceeding the 65535 bytes limit

Stacktrace:
org.apache.jasper.compiler.DefaultErrorHandler.jav acError(DefaultErrorHandler.java:98


아주 오래전에 한번 들어봄직한 자바파일 사이즈가 65535 bytes제한이 있다는 이야기...^^;

이거 실제로 겪어보니 황당하기도 하고, 난감하기도 했다.

서버쪽 비즈니스 로직 코드에 자바파일이 이만큼 큰 경우는 거의 없을 것이다.

하지만, jsp는 화면이 복잡하다가 보면 코드량이 많아져서 발생할수 있는 문제중의 하나이다.


이 문제를 해결하기 위한 방법! js나 css를 파일로 분리한다,
1.  링크형태로 jsp페이지에 넣는다.

2. 반드시 페이지 안에 넣어야 한다면, 베스트 선택은 커스텀 태그이다.
동적으로 페이지 안으로 코드를 넣기 때문에 사이즈가 조금 밖에 증가하지 않는다.

* jsp
에 있는 커스텀태그가 컴파일된 이후 자바파일 코드
private boolean _jspx_meth_b_js_0(PageContext _jspx_page_context)
          throws Throwable {
    PageContext pageContext = _jspx_page_context;
    JspWriter out = _jspx_page_context.getOut();
    //  b:js
    com.naver.blog.foundation.web.taglib.HtmlResourceIncludeTag _jspx_th_b_js_0 = (com.naver.blog.foundation.web.taglib.HtmlResourceIncludeTag) _jspx_tagPool_b_js_path_nobody.get(com.naver.blog.foundation.web.taglib.HtmlResourceIncludeTag.class);
    _jspx_th_b_js_0.setPageContext(_jspx_page_context);
    _jspx_th_b_js_0.setParent(null);
    _jspx_th_b_js_0.setPath("/common/javascript/common.js");
    int _jspx_eval_b_js_0 = _jspx_th_b_js_0.doStartTag();
    if (_jspx_th_b_js_0.doEndTag() == javax.servlet.jsp.tagext.Tag.SKIP_PAGE) {
      _jspx_tagPool_b_js_path_nobody.reuse(_jspx_th_b_js_0);
      return true;
    }
    _jspx_tagPool_b_js_path_nobody.reuse(_jspx_th_b_js_0);
    return false;
  }
상속을 통해서 클래스 갯수를 줄이고 있던 디자인이 있었다. 거의 7개 가량의 갯수를 줄이기 위해서 디자인이 깨지는 것을 보면서도 이게 실용적(?)이라고 생각을 하면서 클래스 갯수를 줄였지만, 영 마음이 불편했다. OOP의 원칙을 깨가면서 만든 디자인이었기때문이다.

불편한 마음을 없애기 위해서 과감하게 클래스 갯수를 늘렸다. 일단, 원칙을 지키면서 점진적으로  refactoring을 하기로 결심을 했다. 대략적인 윤곽은 마음 속에 있었지만, 사실 목적은 분명하지 않은채 점진적으로 변경을 꾀하면서 변경된 코드 안에서 좀더 나은 디자인으로 개선을 해나갔다.

대략의 과정을 설명을 하자면,
구현 상속으로 되어 있던 디자인을 delegate를 통해서 데코레이션 패턴(나..이 패턴 너무 좋아한다..^^;)으로 변경하고 , 인터페이스만을 wiring하는 클래스를 만들고, dependency injection을 spring config에서 설정을 통해서 인스턴스 생성과 Factory에 등록을 했다.

디자인을 변경 후 3가지의 장점을 가질 수가 있었다.

1. 테스트가 쉬워졌다.
구현상속은 많은 dependency를 가지게 된다. dependency를 하나의 인터페이스로 추상화가 가능했고, 하나의 mock을 생성해서 테스트를 할 수가 있었고, 확장하는 클래스에서 필요한 클래스들만 mo필ck으로 만들어서 테스트를 하면 되었기때문에 테스트를 작성하기 쉬웠다.

2. 코드의 이해가 쉬워졌다.
구현에 대해서 집착을 하지 않아도 되고, 클래스 자체만 봐도 이해를 하기 쉬웠다. 다른 클래스들과의 관계는 이 클래스를 이해하는데, 별다른 장벽이 되지 않았다.

3. 객체지향원칙을 지킬 수 있었다.
부모 클래스에서 확장되어지는 자식 클래스때문에 로직이 추가가 되면 안된다. 비즈니스 로직 특성상 피하기 어려웠지만, 최소한의 변경으로 디자인이 깨지는 것을 피할 수 있었다. 즉, 자식과 관련되서 if문이 증가하지 않았다.

아마 spring configuration을 사용하지 않았다면, 클래스 갯수를 7개정도 늘려서 DI를 해주는 상황이었다. 원칙에 따라서 디자인을 개선을 하고 나니 나름 볼만한 코드가 되었다. 클래스 갯수가 총 8개가 늘어나야 할 상황이었지만, 7개에 해당하는 dependency부분을 spring IoC가 해결을 해주었고, 인터페이스를 wiring하는 심플한 하나의 클래스만 늘어나게 되었다. spring config를 통한 IoC가 아니었어도, 클래스 갯수를 늘려서 확장하는 것이 좋은 선택이었을 것이다. 그 좋은 선택 안에서 spring IoC는 좀더 나은 기쁨을 준 것이다.

사실, 서비스되고 있는 코드를 refactoring하는 것은 쉬운 일은 아니지만, 계속된 찜찜한 마음의 짐을 떨치고 싶었다. 좀더 자신감을 가지고 변경을 꾀할수 있었던 것은 테스트가 된 구현 클래스들을 인터페이스 기반으로 조립하는 일이었기때문에 가능했다. 구현을 다시한 부분은 거의 없고, 클래스들 간의 관계를 바꾸는 디자인 개선작업이어서 좀더 편하게 변경을 가할수 있었다.



대표적인 것은 클래스의 멤버변수로 있는 Collection인터페이스이다.
어떤 책인지 기억이 잘 나지는 않지만, 이미 여러군데의 책에서 다룬 내용이기는 하다.

아래에서 다룰 내용은 추상화, 캡슐화에 대한 이야기다.

클래스에서 get을 써서 외부에 어떤 behvior나 자신의 상태를 expose를 할 때 주의를 해야 할 상황이기도 하다.

첫번째 코드

public class SimpleSet{

   private List<Simple> simpleList = new ArrayList<Simple>();
  
   public List<Simple> getSimpleList() {
       return simpleList ;
   }
}


위와 같은 코드의 문제점은 무엇인가? 우리가 쉽게 쓴 패턴이다. 하지만, 클라이언트 코드에서 simpleList의 레퍼런스를 받은 후 수많은 조작이 가능하다.

심지어 이런 코드도 가능하다.
simpleList.clear()

필요하다면 SimpleSet에 clear를 행위로 만들어줘야한다.
직접적인 호출은 수많은 버그들을 만들 가능성이 커진다. 의도하지 않는 곳에서의 조작으로 디버깅도 어렵게 만든다.

두번째 코드

public class SimpleSet{

   private List<Simple> simpleList = new ArrayList<Simple>();
  
   public List<Simple> getSimpleList() {
       return Collections.unmodifiableList(addedInfoList);
   }
}


위와 같은 코드의 문제점은 무엇인가? 리스트를 바꿀 수 없게 collection API는 위와 같은 proxy(decorator) pattern의 인터페이스를 제공한다. 이제는 외부에서 simpleList 를 readonly만 접근이 가능하다.

세번째 코드

public class SimpleSet{

   private List<Simple> simpleList = new ArrayList<Simple>();
  
   public Iterator<simple> iterator(){
       return this.simpleList .iterator();
   }
}

한번더 생각을 해보자. 외부로 제공할 때 iterator패턴으로 위와 같이 제공을 한다면, 어떨까? 내부에 collection의 인터페이스마져 숨길 수가 있다. 항상 리스트만이 내부적인 알고리즘이나 로직을 처리하기 위해서 최선이라고 얘기를 할수는 없다.

SimpleSet의 로직을 처리할 때 List보다 더 나은 자료구조가 생겼을 때 아주 쉽게 갈아탈수가 있다. 위와 같은 코드는 가장 추상화가 잘된 클래스이다.

사실 첫번째 코드가 아니라면, 두번째나 세번째 코드를 권장하고 가능한 세번째 코드를 이야기를 하고 싶다. 두번째 코드도 collection자체가 워낙에 유연해서 쉽게 만들수 있는 인터페이스이기 떄문이다. 물론, 출력자체를 iterator로 하는 거 자체에 많은 이점도 있기때문에 개발자가 심사숙고해서 선택할 expose 인터페이스이다.

하지만, 첫번째 코드를 사용하지 말자!!

참고문서
http://java.sun.com/j2se/1.4.2/docs/api/java/util/Collections.html

가끔 코딩을 하다가 보면 unique한 값을 사용해야 할때가 있다.

여러가지 조합으로 만들기도 하지만, 자바 자체의 API를 이용해서 좀더 편하게 만들수 있을 것이다.

A UID represents an identifier that is unique over time with respect to the host it is generated on, or one of 216 "well-known" identifiers.

  • unique, an int that uniquely identifies the VM that this UID was generated in, with respect to its host and at the time represented by the time value (an example implementation of the unique value would be a process identifier), or zero for a well-known UID
  • time, a long equal to a time (as returned by System.currentTimeMillis()) at which the VM that this UID was generated in was alive, or zero for a well-known UID
  • count, a short to distinguish UIDs generated in the same VM with the same time value

    http://java.sun.com/j2se/1.5.0/docs/api/java/rmi/server/UID.html


    VMID is a identifier that is unique across all Java virtual machines. VMIDs are used by the distributed garbage collector to identify client VMs.

    VMID()
    Create a new VMID. Each new VMID returned from this constructor is unique for all Java virtual machines under the following conditions: a) the conditions for uniqueness for objects of the class java.rmi.server.UID are satisfied, and b) an address can be obtained for this host that is unique and constant for the lifetime of this object.


    http://java.sun.com/j2se/1.5.0/docs/api/java/rmi/dgc/VMID.html

    분산환경에서 여러개의 VM이 존재를 한다면, UID보다는 VMID를 추천하고 싶다.


    샘플 코드르 작성해봤다.

    User inserted image


    이건 실행결과다. 원하는 필드의 사이즈에 맞게 적절하게 사용하면 되겠다.

    User inserted image

  • 안영회님의 흥미로운 주석의 위치 라는 글을 읽다가 전에 오픈소스를 보다가 주석의 위치에 감동이 받은 적이 있어서 그것에 관련된 얘기를 적어볼까 한다.

    아래와 같은 주석은 1.5 generic에서는 활용도가 떨어지지만, 주석에 대한 사고를 넓혀주기에 충분한 예제라고 생각이 들어서 적어본다.

    ex1)
    List  /*Person*/ getListAll();

    ex2) 1.5에서는 generic이 가능해서 주석이 필요가 없다.
    List<Person> getListAll();

    언어차원에서 지원이라고 하는게 더 적합하겠다.

    과거에는 List라는 곳에 무엇이 들어있는가 궁금해서, getPersonListAll()이라던가, 그 안에 들어갈 객체를 다른 방법을 써서 표시를 해야 했는데, 주석을 코드 사이에 넣어주면, 좀더 이해하기가 쉬운 코드가 된다.

    고정관념을 깨뜨리는 것은 좀더 인간이 이해하기 쉬운, 코드로서 설명을 할수 있는, 별도의 문서화가 필요가 없는 코드를 작성을 할수 있을 것이다.

    근데, 너무 오바하면 가독성을 더 해치기도 한다....;)


    세상의 이치는 모두 일치를 합니다. 전산도 철학이라고 얘기를 할수가 있습니다.

    철학적인 이야기나 전산학적인 이야기나 같은 개념으로 하나로 통일되는 이야기들이 있습니다.

    명확한 표현의 이야기와 코드는 의미가 좁은 범위의 단어를 사용해야 이해를 하기가 쉬울것입니다.

    전역적인 컨텍스트 객체를 잘 활용하는 것은 최소한의 사용일 것입니다. 필요한 곳에 적당하게 잘 활용을 해야만 합니다. 홍길동처럼 신출귀몰 등장하는 Context객체는 역겨울뿐입니다.

    너무 많은 관계를 만들지 말고, 최소한의 관계를 이루는 것은 SRP만큼이나 중요하다는 생각이 듭니다.

    내 코드에 IMPORT되는 클래스는 몇개인지를 다시 한번 체크해 볼 필요가 있습니다. 관심있는 클래스는 같은 패키지 존재를 해야 합니다.

    EP님의 글을 읽고 이렇게 글을 쓰고나니 내가 만든 코드에 고칠 곳들과 버릴 코드들이 마구 떠오르는 군요.

    의미와 면적이 좁은 단어를 사용하라 - EP
    http://colus.egloos.com/2765179

    POJOs in Action

    2006/10/11 23:46
    POJOs in ACTION

    우리가 개발하는 여러가지 프로그래밍 모델을 하나의 책으로 만들어서 내놓은 책이다.

    일단 저자의 다양한 경험 묻어나온 문장들과 실질적으로 프로젝트 시작전에 하는 일들을 예시로 들고 있어서, 공감이 가는 부분도 많다.

    chapter3,4장 부분은 Domain Driven Design(Eric Evans)을 하나의 개발 패턴으로 보고, 얘기를 했는데, DDD책의 요약판정도의 개념이 들어가 있다.

    EJB없이 J2EE를 하는 방법을 과거에 서적에서는 무척이나 찾기 힘들었다. POJO를 하려고 해도 EJB서적이나 패턴들을 가지고, NON-EJB로 풀어나갈때 고민하던 일들이 많았을 것이다.

    최근에는 Spring을 바람과 함께 POJO 프로젝트가 늘어나고 있는데, 프로젝트 진행시 선택해야 할 사항에 대해서 자세히 다루었는데, 그 고민들이 나만의 것이 아닌 많은 사람들이 하고 있었다는 것을 알수 있다.

    이 책과 함께 POJO의 마력에 빠져봅시다~!!



    BLOG main image
    OOP and Java by ologist

    공지사항

    카테고리

    All (649)
    private!! (106)
    WEB & IT (140)
    Developer (400)