AssertThrows

Spring의 Test모듈에 들어있는 그다지 잘 알려져 있지 않은, 그러나 매우 유용한(유용하지 않은게 있긴하나? TestNG… 정도 :) 클래스 중에 AssertThrows이라는 것이 있다. AssertThrows는 보통 try/catch 블럭을 이용해서 만드는 익셉션이 발생하는 것을 확인하기 위한 테스트 코드를 스프링의 callback/template 스타일을 이용해서 깔끔하고 명료하게 작성할 수 있게 해준다.

일반적인 try/catch를 이용한 코드이다. try/catch 블록이 지저분 할 뿐더러, fail을 깜빡 빼먹어서 테스트를 잘못 작성하기도 쉬울 뿐더러, 익셉션 발생을 기대하는 코드로 자연스럽게 읽히지 않는다는 것이 단점이다.

try {
  // exception이 발생해야 하는 코드들...
  fail(“XXXException expected but…”);
} catch(XXXException e) { 
}

위의 경우는 XXXException이 발생하지 않거나, 다른 예외가 나면 테스트가 실패한다. 다른 종류의 예외의 경우 메소드 밖으로 던져지게 해서 테스트 실패가 아니라, 에러로 처리되는 것이 보기 좋지 않다면 Exception 타입으로 모두 catch한 후에 exception의 종류를 다시 한번 assert해주는 방법도 좋다.

catch(Exception e) {
  assertTrue(XXXException.class.isAssignableFrom(e));
}

이를 AssertThrows를 이용해서 작성하면 아래와 같다. 첫줄부터 명확하게 어떤 예외가 발생하는 것을 assert하는 코드라는 것이 자연스럽게 읽히고, 실패하는 경우 AssertFailureError가 발생하는 것이 확실하게 보장된다. 익명내부클래스(anonymous inner class)를 사용하지만 try/catch 블럭보다 보기도 깔끔하다.

new AssertThrows(XXXException.class) {
  public void test() {
    // exception이 발생해야 하는 코드들...
  }
}.runTest();

AssertThrows는 전형적인 callback/template 방식의 코드이다. test를 수행하는 코드를 hook method로 설정하고, try/catch와 예상되는 익셉션을 비교하고 적절한 에러메시지를 만들고, rootCause등을 세팅하는 것 등의 지저분한 일들을 내부의 템플릿 코드에서 모두 처리한다.

사용자는 이 클래스를 상속해서 test() 메소드를 오버라이드 해서 테스트코드를 작성하고, 원하는 익셉션을 생성자에 넣고 오브젝트를 만들어  runTest()  코드를 실행하면 된다.

Template Method 패턴을 이용한 것인데, 클래스 구조를 가진 패턴이라 매번 상속을 해야 하는 번거로움을 자바의 익명내부클래스를 이용해서 처리하니 거의 오브젝트 구조의 패턴만큼 간결하고 편하게 사용할 수 있다. 약간 목적은 다르지만 JUnit의 아답터 패턴이 적용된 pluggable behaviour 방식의 코드를 보는 듯 하다. JUnit extension에 아마 이와 비슷한 것이 있었던 기억이 있다(찾아보기 귀찮아서 .. 아마..)

 

스프링의 내부 테스트에서는 이 AssertThrows가 애용되고 있다. 프레임워크라는 특성상 예외를 확인해야 하는 로직들이 매우 많기 때문이다. 나또한 자주 사용한다.

사실 JUnit 4에서는 이렇게 익셉션 발생을 기대하는 테스트를 @Test(expected=…)으로 만들 수가 있다. 하지만 JUnit 3.8을 사용하거나 4.x를 사용하더라도 한 테스트 메소드 내에서 여러개의 assert과정의 일부로, 또는 반복적으로 예외를 체크할 경우는 @Test(expected=…)는 사용하기 힘들다. 그래서 나는 주로 JUnit 4를 사용하지만 AssertThrows를 애용한다. 무엇보다도 스프링의 스타일이 잘 드러나는 코드가 맘에 든다.

 

얼마전 Y군이 보라고 준 M잡지에 실린 테스트와 관련된 글을 하나 읽다가 AssertThrows에 관한 소개나 나오는 것을 보고 반가운 마음이 들었다. 예외가 발생해야 하는 상황에 대한 테스트 방법이라고 소개되어있다.

그런데 이어서 나오는 내용을 보고 조금 당황스러웠다. 단순히 예외 타입을 체크하는 것 이상의 테스트가 필요한 경우, 예를 들어 에러코드나 메시지까지도 확인해야 하는 경우는 AssertThrows를 쓸 수 없으니 그냥 구식 try/catch 방식의 테스트 코드를 작성하라는 것이다.

그리고 친절하게 예제가 나와 있다.

try {
  target = this.businessCalendar.
  getWorkingDateByTime(null, 11);
} catch (ApplicationException be) {
  assertEquals("유효하지 않은 에러코드입니다.", 
    BusinessCalendarMessageConstants.BIZ_CAL_DATE_NULL, be.getMessageCode());
} catch (Exception e) {
  fail("예외 타입 오류입니다.");
}

 

나는 이 테스트 코드에 대해서 두가지 불만이 있다.

첫째는 이 테스트의 의도가 불명확하다는 것이다. try 블럭에서 익셉션이 발생하지 않으면 테스트가 성공한다. 또한 테스트가 발생했는데 ApplicationException이고, 그 에러코드가 특저정 값인 경우도 테스트가 성공한다. 그 외에는 모두 실패한다. 이 테스트가 특정 에러코드를 리턴하는 익셉션을 체크하기 위한 테스트인지, 아니면 그 특별한 경우를 포함해서 정상적으로 동작하는 경우를 확인하기 위한 테스트인지 모호하다.

두번째는 스프링이 제공하는 편리한 기능의 AssertThrows를 사용했지만, 테스트 조건이 조금 바뀌었다고 바로 이전의 방식으로 돌아갔다는 점이다. AssertThrows와 같은 callback/template/pluggable behavior 스타일의 접근 방법은 스프링에서 매우 많이 사용되는 스타일이다. 비록 스프링이 익셉션의 특정한 조건을 부가적으로 제크하는 기능을 AssertThrows에는 만들어 놓지 않았지만, 그렇다고 해서 미리 만들어 놓은 기능이 없으니 그냥 이전에 하던 대로 하자는 것은 너무 수동적이고 무기력한 모습이 아닐까?

 

AssertLabledThrows

사실 나는 이미 오래전부터 익셉션의 메시지까지 확인하는, AssertThrows를 확장한 클래스를 만들어서 사용하고 있었다. 이름은 AssertLabledThrows이다. 단지, 비즈니스 로직의 예외를 체크하기 위해서 뿐만 아니라, 테스트하기 힘든 케이스(예를 들면 AOP의 특정 어드바이스가 실행되었는지를 확인하는 등의)를 위해서 특정 포인트에서 상태를 메시지로 만들어서 익셉션을 던지게 하고 AssertLabledThrows에서 특정 익셉션과 메시지를 모두 체크하도록 만들었다.

이를 이용해서 JavaConfig의 라이프사이클 메소드 호출이 되는 기능을 확인하기 위한 학습테스트로 만든 코드는 이렇다.

new AssertLabledThrows("initMethod") {
	public void test() throws Exception {
		new JavaConfigApplicationContext(AppConfigWithInitMethod.class);
	}
}.runTest();

@Configuration
static class AppConfigWithInitMethod {
	@Bean(initMethodName = "initMe")
	public BeanD myBeanD() {
		return new BeanD();
	}
}

static class BeanD {
	public void initMe() {
		throw new TestException("initMethod");
	}
}

 

특정 메소드에서 미리 지정된 익셉션을 던지게 하고 그 안에 구분이 가능한 에러메시지를 넣은 후, 이를 AssertLabledThrows 안에서 익셉션 타입과 메시지까지 함께 확인하는 것이다.

이 AssertLabeldThrows는 AssertThrows를 간단히 확장해서 만든 것이다. AssertThrows 클래스를 살펴보면 알 수 있지만, 적절한 템플릿과 로직을 담당하는 메소드는 모두 protected로 되어있어서, 서브 클래스에서 그 동작을 확장할 수 있도록 의도적으로 설계 되어있다. 익셉션 체크, 에러 메시지 작성, 전체 테스트 진행 템플릿 등이 모두 상세하게 독립된 메소드로 되어있어서 세세한 기능을 확장하기가 편리하다.

스프링의 많은 클래스들을 분석해보면 대부분 이런식으로 미의 확장을 위해서 효과적인 구조로 만들어져있다. 스프링을 그저 제공된 API를 기계적으로 사용하기 보다는, 이를 스프링의 개발 스타일을 따라서 그 확장포인트를 이용해서, 실제 개발 프로젝트의 요구사항에 맞게 적절히 확장해서 사용하는 것이 스프링을 제대로 사용하지를 알 수 있는 중요한 포인트이다.

 

그런데 위의 잡지에 소개된 코드를 보면서 내가 접근한 방법에 대해서도 불만이 생겼다. 나는 내가 필요한 특정한 방식의 익셉션 체크를 위해서 AssertThrows를 확장해서 사용하긴 했는데, 그 자체로 범용적인지 않으니, 만약 위의 예제처럼 코드를 비교한다거나, 다른 조건으로 익셉션에 대한 검증이 필요한 경우에 매번 새롭게 확장된 클래스를 구현해야 한다는 번거로움이 있다는 점이다.

 

AssertThrowsTemplate

그래서 이번에는 AssertThrows를 보다 다양한 조건을 가진 경우에도 손쉽게 적용할 수 있도록 좀 더 유연한 클래스로 확장하기로 했다. 이름하여 AssertThrowsTemplate. 스프링에 나오는 일반적인 xxxTemplate스타일로 만들어볼 생각이다.

다만, AssertThrows자체가 다른 Template류와는 다르게 template method pattern을 사용하고 있기 때문에 조금 한계가 있다. 그래도 구지 새롭게 만들기 보다는 가능한 방법을 사용해서 이를 확장하는 것이 낫다고 생각했다. 그래서 주말 저녁시간에 빈둥빈둥 만들어 본 AssertThrowsTemplate의 개발과정을 적어보려고 한다.

 

음.. 더 길어지면 Y군이 길게 쓴다고 또 씹을테니, 다음 포스팅으로 넘겨야 겠다.

Related posts:

  1. Spring 3.0 EL (Spel)을 이용한 AssertThrows 확장 (2)
  2. Spring 3.0 EL (Spel)을 이용한 AssertThrows 확장 (3)
  3. Spring Expressions(SpEL)를 이용한 Mockito Argument Matcher 만들기
  4. Spring 3.0 (59) 프로퍼티 파일 이용하기 – placeholder vs SpEL
  5. Spring 3.0 (58) Static Class를 XML없이 빈으로 등록하기
  6. Spring 3.0 (42) Spring Dependency Matrix 업데이트
  7. Spring 3.0 (55) getBean(Class) 등장
  8. Spring 3.0 (4) – Maven에서 Spring 3.0 최신버전 사용하기
  9. Spring 3.0 (46) Spring 3.0 M4 릴리스
  10. Spring 3.0 (28) R-669 Update
  11. Spring 3.0 (60) 클래스패스 리소스를 지정할 때 주의사항과 팁
  12. Spring 3.0 (54) 드디어 등장한 ConfigurationClassApplicationContext
  13. Spring 3.0 (25) Spring 3.0 빌드, 배포, 모듈과 라이브러리의 의존관계 분석 그 이후
  14. Spring 3.0.2 모듈/라이브러리 의존관계
  15. Spring 3.0 (35) Spring 3.0 Reference Document 공개

Facebook comments:

to “Spring 3.0 EL (Spel)을 이용한 AssertThrows 확장 (1)”

  1. PRHqq4 hkhlifojeapr, [url=http://ljearnkeglmt.com/]ljearnkeglmt[/url], [link=http://eszupaldeoid.com/]eszupaldeoid[/link], http://hvxxbeggwzts.com/

  2. Browse Around THIS Site

  3. We have decided to open our POWERFUL and PRIVATE website traffic system to the public for a limited time! You can sign up for our UP SCALE network with a free trial as we get started with the public’s orders. Imagine how your bank account will look when your website gets the traffic it needs. Visit us today: http://nsru.net/188x

  4. You Could Check HERE
    [url=http://yuehaomicro.com/manage/show.php?/nike-air-max-360-ii-amazon-fashion-that-suits-all-2013.html]nike air max 360 ii amazon-Fashion That Suits All 2013[/url]
    nike air max 360 ii amazon-Fashion That Suits All 2013

  5. Investigate THIS Site
    [url=http://australiaukbootsale.com]ugg australia uk[/url]
    ugg australia uk

  6. mbt tataga shoes Spring 3.0 EL (Spel)을 이용한 AssertThrows 확장 (1) » Toby’s Epril

  7. shoes online Spring 3.0 EL (Spel)을 이용한 AssertThrows 확장 (1) » 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