JUnit 4.7이 공개됐다.

초기에 @Interceptor라는 이름으로 소개됐던 기능이 막판에 @Rule이라고 이름을 바꾸고 실험기능이 아닌 코어기능으로 바로 들어가면서 등장했다. 애노테이션을 확인해서 테스트를 실행하는 방식으로 완전히 바뀌었던 JUnit4.0이후 가장 큰 변화이다.

JUnit의 핵심클래스인 BlockJUnit4ClassRunner에 있는 테스트 메소드를 실행하는 과정을 담은 methodBlock()에 큰 변화가 있었다.

기존에는 After->Before->Timeout->Expected->테스트메소드 실행이라는 테스트메소드 실행규칙을 하드코딩해서 내부에 가지고 있었다. Nested Object Method니 Conditional Factory니 하는 이름이 무색하게도 테스트를 실행하는 규칙의 조합과 순서는 고정되어있었고 확장이 불가능했다.

이를 굳이 확장하려면 Theories처럼 BlockJUnit4ClassRunner를 상속한 후에 super의 methodBlock()을 먼저 호출하고, 자신의 기능을 추가하는 방법을 사용하든가(Theories가 테스트 실행순서의 가장 바깥에 위치하기 때문에 가능한 것이다), 아니면 스프링의 SpringJUnit4ClassRunner처럼 BlockJUnit4ClassRunner 상속한 후에 주요한 메소드의 코드를 그대로 배낀 후에 일부만 고쳐서 사용해야 했다(스프링은 테스트 실행 순서가 바꾸기 때문에 Theories 같은 방식을 쓸 수 없다)

필터,인터셉터,COR,데코레이터,Nested Method Object 등등 뭐라고 불러도 좋지만 아무튼 다음 단계에 대한 포인트를 가지는 구현을 사용하면서 Statement라는 단일 인터페이스를 구현해서 이를 중첩시키는 방법을 썼다면 충분히 그런 설계의 장점인 런타임시 다이나믹한 재구성 가능성을 염두에 두었어야 했다.

하지만 기존 Junit4.6까지의 코드는 기껏 그런 멋진패턴과 인터셉터 구조를 만들어 놓고도 그것을 핵심코드에서 실행순서를 고정해버리는 한심한 선택을 했다. 때문에 단지 그런 실행과정을 확장하기 위해서 테스트 러너의 코드 전체를 상속해서 오버라이드를 통해서 뜯어고쳐야 하는 방법 외에는 유연한 확장의 길을 막아버린 것이다. 게다가 핵심기능을 그렇게 마구 고치다보면 RSP를 위반할 가능성이 매우 높아진다.

아래 메소드가 4.6까지의 핵심 테스트 실행규칙을 고정적으로 담고 있는 BlockJUnit4ClassRunner의 메소드이다. 각 단계를 거치면서 애노테이션이나 엘러먼트를 확인해서 Statement오브젝트를 중첩하는 구조로 만들어진다. 최종 작업인 테스트 메소드 실행이 리플렉션으로 일어나기 때문에 타겟의 인터페이스를 구현하는 방식으로 만들어지지는 않지만 내용을 보자면 전형적인 책임부가를 위한 데코레이터 패턴 또는 인터셉터의 구조이다.

protected Statement methodBlock(FrameworkMethod method) {
   …

    Statement statement= methodInvoker(method, test);
    statement= possiblyExpectingExceptions(method, test, statement);
    statement= withPotentialTimeout(method, test, statement);
    statement= withBefores(method, test, statement);
    statement= withAfters(method, test, statement);
    return statement;
}

문제는 이렇게 핵심클래스를 코드로 고정시켜버리면 유연한 확장의 여지가 없다는 점이다. 켄트 벡의 말대로 JUnit4가 혁명적인 변화를 통해서 맨땅에서부터 새로 만들어졌기 때문에 코드는 인라인화되었고 중복도 용인할 수 있는 혼란의 상태에 여전히 있다는 것일까?

Sometimes an effective revolutionary strategy is just to inline absolutely everything in a class and helpers into a single method and begin re-extracting from there.

 

이제 4.7에 이르러서 겨우 테스트를 실행하는 단계와 그 내용에 대해서 @Rule이라는 이름으로 모듈화를 하고, 재구성 할 수 있고, 분리하는 것이 가능해졌다. 위의 메소는 JUnit4.7에서 다음과 같이 바뀌었다.

protected Statement methodBlock(FrameworkMethod method) {
    ….

    Statement statement= methodInvoker(method, test);
    statement= possiblyExpectingExceptions(method, test, statement);
    statement= withPotentialTimeout(method, test, statement);
    statement= withRules(method, test, statement);
    statement= withBefores(method, test, statement);
    statement= withAfters(method, test, statement);
    return statement;
}

기존에 코드에 박혀있었던 테스트 실행 단계 4가지는 이제 @deprecated되었다. 최종 테스트를 실행하는 Statement인 MethodInvoker를 부여하는 것과 테스트 실행전의 인터셉터에 해당하는 Rule을 다이나믹하게 적용할 수 있도록 바뀌었다. @deprecated된 메소드들을 보면 조만간 이 메소드들은 전혀 확장의 여지가 없게 private으로 바뀔 것이고(호환성을 고려해 차마 없애지는 않나보다) 대신 Rule을 사용하라고 되어있다. 적어도 테스트를 어떻게 실행하는지에 대해서는 이제 확장의 여지가 들어간 것이다.

그럼에도 아직 JUnit4.7의 코드도 여전히 핵심적인 기능들은 BlockJUnit4ClassRunner에 박혀있다. Rule을 적용하는 방법은 @Rule이 붙은 필드로 제한되어있다. 그 Rule을 읽어와 중첩 Statement를 구성해주는 코드가 BlockJUnit4ClassRunner에 박혀있기 때문이다. 왜 클래스 단위로 테스트를 실행해주는 책임을 가진 클래스에 테스트 실행 규칙을 어떻게 읽어오는지에 대한 책임도 들어있고, 이를 어떻게 구성하는지에 대한 책임도 들어있는지 모르겠다.

 

켄트 벡은 OCP는 혁명적인 변화가 있을 때는 필연적으로 위반할 수 밖에 없다고 항변한다. JUnit3에서 JUnit4로 바뀐 것에 대해서라면 동의한다. 이건 OCP위반정도가 아니라 목적만 비슷할뿐 아답터를 쓰지 않고는 서로 호환도 안되는 완전히 새로운 프레임워크를 만든 것이니깐.

하지만 JUnit4에 들어와서 4.7에 이르기까지의 변화에 대해서도 같은 이유로 OCP를 위반하는 것이 어쩔 수 없다고 주장한다면 나는 동의할 수 없다. 적어도 @Rule 방법을 도입하면서 핵심코드를 수정할 수 밖에 없었던 이유는 혁명적인 변화 때문이라기 보다는 충분히 고려할만한 수준의 유연한 확장을 위한 설계가 JUnit4.6까지 존재하지 않았기 때문이라고 생각한다. 만약 테스트 실행방법에 대해서 러너 외부에서 이를 규정할 수 있도록 분리하고 위임하는 설계를 만들었다면 @Rule의 도입정도는 얼마든지 기존 코드를 건드리지 않고 간단한 추가를 통해서 가능했던 일이다.

스프링은 애노테이션을 통한 DI나 @Configuration가 달린 자바코드를 통한 DI 라는 혁명적인 변화조차도 1.x부터 설계해놨던 컨테이너의 PostProcessor 확장포인트만들 이용해서 기존 컨테이너 코드에 영향을 주지않으면서 새로운 기능의 확장을 가져올 수 있었다. ApplicationContext의 핵심클래스들은 스프링이 나오기 한참 전인 2001년부터 만들어서 쓰던 것들이다. AspectJ의 포인트컷 표현식이나 @Transactional 같은 애노테이션으로 통합된 트랜잭션 속성+포인트컷의 결합기능이 추가될 때도 스프링의 1.x부터 존재했던  Pointcut, Advice, Advisor의 확장 구조를 이용하는 것만으로 충분했다. 스프링이라고 완벽한 것은 아니겠지만, 적어도 프레임워크라는 입장에서 극대화된 확장성을 충분히 고려해서 모든 것을 설계해 두었기에 개발된지 6년째 3.0이 될때까지 코드를 갈아 엎는 일 한번 없이 거의 완벽에 가까운 구버전호환성을 지키면서도 최신 기능과 새로운 동작방식을 끝도없이 추가할 수 있었던 것 같다.

 

아무튼, 나는 기존 JUnit4.6까지의 코드가 사실은 OCP를 따르지 않았기 때문에 기능을 확장하기 위해서 러너의 코드를 변경할 수 밖에 없었던 것이라고 본다. 자유로운 테스트 실행방법의 기능을 확장하는 것에는 굳게 닫혀있고, 그 때문에 핵심코드를 변경하는 것에는 혼란스럽게도 열려있었을 뿐이다.

JUnit4가 처음부터 조금만 더 유연한 사고를 가지고 설계되었더라면 좋았을 것이라는 아쉬움을 항상 가지고 있다. 이제 JUnit4.7의 변화에 대해서 SpringJUnit4ClassRunner는 과연 어떻게 바뀔지 궁금하다. 혹시 4.6이전버전과 4.7이후 용으로 따로 만들어지는 것일까? JUnit이 OCP를 따르지 않은 탓에 스프링과 같이 무식한 방법을 동원해서 겨우 JUnit4 확장 러너를 만들 수 밖에 없었던 경우에는 앞으로 상당한 혼란을 겪을지도 모르겠다. 4.x대는 4.7을 실험적인 버전으로 놔두고, 더욱 유연한 Rule기반의 5.x를 설계하는 것이 어떨까 싶은데, Junit메일링 리스트를 보면 4.8에 대한 추가기능에 대한 이야기를 하고 있으니 앞으로 얼마나 더 나갈지 모르겠다.

 

참, @Interceptor라는 이름을 막판에 @Rule로 바꾼 이유에 대해서 켄트 벡은 "메카니즘 보다는 의도를 가지고 이름을 지어야 한다는 원칙을 깜빡했었다"라고 설명했다. Rule이라는 게 그다지 의도가 잘 느껴지지는 않지만 어쨌든 인터셉터보다는 낫다. Struts2를 처음 봤을 때 인터셉터에 핵심기능들을 담는 다는 것이 잘 이해가 되지 않았던 것도 그런 이유일 것이다.

메커니즘보다는 의도를 가지고 이름을 정하라는 원칙는 DI의 번역에도 적용할 수 있다고 생각된다. DI를 의존(종속)객체주입이라고 번역하는 사람들의 주장은 그 것이 메카니즘을 잘 반영한다는 것이다. 하지만 그래서는 충분한 DI의 의도를 담지 못한다는 게 내 생각이다. Manning의 Dependency Injection책을 쓰고 있는 구글웨이브의 개발자인 D어쩌고(이름이 당최 외우기가 힘들어서) DI의 Dependency를 의존서비스라고 설명한다. 원래 용어가 만들어진 의도를 제대로 드러내면서 번역하자면 의존성주입은 아주 적절한 용어이다이다.  의존성이라는 말이 모호해서 의도를 잘 드러내주지 못한다면 의존관계주입(설정) 또는 의존서비스주입이라고 해도 될 것 같고.

그나저나 스프링은 무슨 의도를 가지고 지은 이름인지 그게 갑자기 궁금하다.

Related posts:

  1. JUnit 4.7 분석 (1) JUnit 4.7 코드 첫인상
  2. JUnit assert 매쉬업
  3. Spring 3.0 (50) JUnit 4.5~4.7 겨우 지원성공 그리고 계속되는 악몽
  4. JUnit 4.7 분석 (0) 시작
  5. Spring 3.0 (49) JUnit 4.7로 변경과 상속의 폐해
  6. JUnitMax Core가 포함된 JUnit 4.6 릴리스
  7. JUnit Max – Kent Beck
  8. 테스트되지 않은 코드는 쓰레기인가?
  9. Spring 3.0 (51) @Rule 지원성공 그리고 Formatter, ConversionService의 전면 등장

Facebook comments:

to “JUnit 4.7과 OCP”

  1. 자바는 무슨 의도를 가지고 지은 이름인지 그게 갑자기 궁금합니다 -0-;

  2. 자바 커피 먹다가.

  3. Good to see real epxertise on display. Your contribution is most welcome.

  4. Is that really all there is to it because that’d be flabbrgeasting.

  5. mbt swiss JUnit 4.7과 OCP » Toby’s Epril

Leave a Reply

(required)

(required)

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

© 2017 Toby's Epril Suffusion theme by Sayontan Sinha