본문 바로가기

지앤선 이야기

[한글화 프로젝트] 2. 단위 테스트 활용 방법: JUnit 참조 가이드(Unit Testing Best Practices: JUnit Reference Guide)




  • 번역가 : 최한뫼
  • forhim185@gmail.com | 삼성전자 모바일 디비전에서 5년간 근무하였고 현재는 University of Melbourne에서 소프트웨어 공학 석사 과정에 있다. Haskell, Scala 같은 Fumctional Programming에 관심이 많다
  • [원문 링크]










단위 테스트 활용 방법: JUnit 참조 가이드 

Unit Testing Best Practices: JUnit Reference Guide


 

(저자는 독자가 JUnit 대한 기본적인 지식이 있을 것이라고 가정하고 이 글을 적었다. 만약 해당 지식이 없다면 우선 웹페이지http://junit.sourceforge.net/doc/cookbook/cookbook.htm 읽기를 추천한다. 저자는 글을 통해 개발자가 단위 테스트를 작성할 때 고려해야 사항들을 기술 것이다.)

 

비용을 천문학적으로 증가시킴에도 불구하고 프로젝트에 정말 아무런 도움이 되지도 않는 단위 테스트를 작성하기란 정말 쉽다.”


이 글의 핵심 내용 : 

·      단위 테스트는 버그를 찾기 위한 것이 아니다.

·      좋은 단위 테스트를 작성하기 위한 팁들

·      하나의 테스트 케이스는 단위 기능중 하나의 시나리오만 테스트하라. 

·      불필요한 검증 구문은 작성하지 마라.

·      각 테스트는 독립적이어야 한다.

·      테스트에 필요한 모든 외부서비스와 상태들은 스텁으로 제공되야 한다.

·      시스템 설정파일에 관한 단위 테스트를 작성하지마라.

·      단위 테스트 케이스의 이름은 명확하고 일관되게 테스트의 의미를 반영해야한다.

·      외부 시스템이나 서비스에 대한 의존성이 가장 낮은 메소드들에 대해 테스트를 먼저 작성하라. 그리고 확장         해 가라.

·      프라이빗 메소드를 포함한 모든 메소드들은 가시범위에 상관없이 적절한 단위 테스트들을 작성해야 한다.

·      각각의 단위 테스트 메소드는 정확히 하나의 검증구문을 가져야 한다.

·      예상된 예외 사항을 테스트하는 단위 테스트 코드를 작성하라.

·      가장 적합한 검증 구문을 사용하라.

·      검증 구문 파라미터들은 적합한 순서대로 배치하라.

·      테스트를 위한 코드는 제품 코드에서 분리되어야 한다.

·      단위 테스트 내에서 아무것도 출력하지 마라.

·      정적 변수를 테스트 클래스에 사용하지 마라.

·      예외 발생시 단순히 테스트를 실패하기 위한 catch 구문을 작성하지 마라.

·      간접적인 테스트에 의존하지 마라.

·      위 테스트를 자동으로 실행하게 빌드 스크립트를 작성해라.

·      단위 테스트들의 실행을 생략하지 마라.(@Ignore 어노테이션을 사용하지 마라.)

·      테스트 결과를 XML 형태로 출력하라.

 

소프트웨어 개발에서 단위 테스트Unit testing는 구현코드의 개별 단위의 적합성 혹은 정확성을 확인 하기 위한 방법이다. 이 단위의 정의는 테스트 시나리오에 따라 다를 수 있다 

예를 들어서 C와 같은 절차적 프로그래밍 언어에서는 하나의 단위가 일반적으로 하나의 프로시저 또는 함수이다. 하지만 객체지향 언어에서는 하나의 메소드가 될 수 있다. 단위 테스트에서 하나의 테스트 단위는 테스트 가능한 가장 작은 부분으로 생각하면 무난하다.



단위 테스트는 버그를 찾기 위한 것이 아니다

Unit testing is not about finding bugs

 

단위 테스트의 의도를 정확히 이해하는 것은 중요하다. 단위 테스트는 단순히 버그를 찾기 위한 효과적인 방법이 아니다. 정의에 따르면 단위 테스트는 시스템의 각각의 단위들을 개별적으로 조사하는 것이다. 시스템이 구현되어 실제 환경에서 동작할 때 모든 단위들은 완벽하게 하나의 유기체로 동작해야한다. 하지만 시스템은 각 독립적으로 테스트되는 단위의 단순한 결합 그 이상으로 복잡하고 또한 에러가 발생하기 쉽다. 콤포넌트 X, Y가 독립적으로 잘 작동한다는 것이 이 콤포넌트들이 서로 호환된다든가 혹은 정확하게 조합되어졌다는 것은 아니다.

 

따라서 단순히 버그를 찾기 위한 것이라면 일반적으로 검증자가 일일이 테스트 하듯이 전체 시스템을 실제 통합환경에서 실행하는 것이 훨씬 효과적인 테스트가 될 수 있다. 그리고 이러한 테스트를 자동화 한 것을 통합 테스트라고 하는데, 이것은 일반적으로 단위 테스트와는 다른 기술들을 사용한다.

 

“단위 테스트는 TDD(Test Driven Development)에서 그러한 것처럼 반드시 시스템 디자인 단계의 일부분으로 보아야 한다.” 이렇게 함으로써 시스템 디자이너는 시스템의 가장 작은 단위 모듈을 인식할 수 있고 또한 개별적으로 테스트 할 수 있다.



Tips for writing great unit tests

하나의 테스트 케이스는 단위 기능 중 하나의 시나리오만 테스트하라

Test only one code unit at a time

 

단위 테스트 작성시 가장 중요하게 인식할 점은 테스트 단위가 복수의 테스트 시나리오들을 가질 수 있다는 것이다. 그리고 모든 테스트 시나리오들은 독립적인 테스트 코드로 작성되어져야 한다. 예를 들어 매개변수를 가지고 처리한 값을 돌려주는 함수의 테스트 케이스를 작성한다고 하면, 다음과 같은 테스트 시나리오가 가능할 것이다.

 

1.     첫 번째 파라미터가 널 값일 경우 예외 객체를 반환해야한다.

2.     두 번째 파라미터가 널 값일 경우 예외 객체를 반환해야한다.

3.     두 개의  파라미터 모두가 널 값일 경우 예외 객체를 반환해야한다.

4.     파라미터가 정상 범위 안일 경우 작업 실행 후 결과 값을 반환해야한다.

 

이러한 세분화된 테스트 케이스들은 코드를 수정하거나 리택토링시 효과적이다. 왜냐하면 단위 테스트만 수행하면 코드의 수정이 코드의 의도된 기능을 망가뜨렸는지 확인할 수 있기 때문이다. 또한 기능을 수정한다면 최소한의 테스트 코드만 수정하면 되기 때문이다.


불필요한 검증 구문을 작성하지 마라.

Don't make unnecessary assertions


단위 테스트는 시스템의 특정 단위가 어떻게 동작하는지에 대한 디자인 스펙이지 단순히 단위 내의 코드가 행하는 모든 것을 관찰하는 것이 아니다.

 

단위 내의 모든 것에 대해 검증구문을 작성하지 마라. 대신 테스트 할려고 하는 하나의 시나리오에 집중해라. 테스트 코드를 이렇게 작성하지 않으면 하나의 이유로 여러 테스트 케이스가 실패 할 수 있다. 결국 이것은 프로젝트에 아무런 도움이 되지 않는다. (왜냐하면 테스트 코드를 보고 문제점을 찾을 수 없기 때문이다.)


테스트는 독립적이어야 한다.

Make each test independent to all the others 

다른 테스트에 의존적인 꼬리에 꼬리를 무는 단위 테스트를 작성하지 마라. 이러한 테스트들은 테스트의 근본적인 실패 원인을 테스트 결과를 통해 알 수 없다. 결국 별도의 디버깅 작업을 수행해야 한다. 또한 이러한 상호 의존적인 테스트 코드는 유지보수도 번거롭다. 왜냐하면 하나의 테스트 코드를 수정할 경우 의존성을 가지고 있는 다른 코드로 수정해야 할 경우가 생기기 때문이다.

 

테스트의 선결 조건을 설정하기 위해서는 @Before/@After와 같은 테스트 프레임워크가 제공하는 어노테이션을 사용해라. 만약 서로 다른 테스트 구문을 위해 @Before/@After 어노테이션 구문 안에서 여러 가지 다른 셋팅을 해야 한다면 별도의 새로운 테스트 클래스를 생성하는 것을 고려해라.


모든 외부 서비스와 상태들에 테스트 더블을 사용해라. 

Mock out all external services and state

그렇게 하지 않으면 공통된 외부 조건를 사용하는 테스트 구문들의 결과가 서로에게 영향을 미친다. 결국 테스트 구문 실행 순서에 따라 테스트 결과가 달라지거나 네트워크 망이나 데이터베이스의 조건에 따라 결과가 달라진다.

 

게다가 외부 서비스의 버그들로 인해서 테스트 결과가 실패로 끝날 수 있다.

 

(테스트 구문이 정적 변수들을 변화시키게 하지마라. 어쩔 수없이 변화시켜야 한다면 적어도 테스트 시작 바로 전에 이 변수들을 초기화 시켜라)


시스템 설정파일에 관한 단위 테스트를 작성하지 마라

Don’t unit-test configuration settings

 

정의에 따르면 시스템 설정은 단위 테스트의 범위가 아니다(그래서 그러한 설정값은 별도의 설정파일로 분리된다). 시스템 설정값에 대한 단위 테스트를 작성하고 싶다면 설정값을 읽는 모듈에 대한 하나 혹은 두 개의 경우만 테스트해라.

 

시스템 설정에 대한 모든 경우수를 테스트를 하는 건 결국 하나 밖에 증명하지 못한다“난 복사 후 붙여넣기 할줄 알아요”


단위 테스트 케이스의 이름은 명확하고 일관되게 테스트의 의미를 반영해야한다

Name your unit tests clearly and consistently

 

이것은 언제나 명심하고 실천해야하는 중요한 점이다. 테스트 케이스의 이름은 항상 테스트의 의도가 무엇인지 반영해야한다. 단순하게 테스트 할려고 하는 단위의 클래스와 메소드의 조합을 테스트 케이스의 이름으로 사용하는건 좋은 생각이 아니다. 클래스나 메소드의 이름을 변경해야 하는 경우가 생길 때 테스트 케이스들의 이름도 매번 수정해야 한다.

 

그러나 단위 테스트 케이스의 이름이 단위의 기능을 반영하는 논리적인 이름이라면 단위 기능이 바뀌지 않는 경우에는 테스트 케이스 이름은 언제나 동일하다.

 

아래는 테스트 케이스 이름의 좋은 예들이다.

1) TestCreateEmployee_NullId_ShouldThrowException

2) TestCreateEmployee_NegativeId_ShouldThrowException

3) TestCreateEmployee_DuplicateId_ShouldThrowException

4) TestCreateEmployee_ValidId_ShouldPass


외부 시스템이나 서비스에 대한 의존성이 가장 낮은 메소드들에 대해 테스트를 먼저 작성하라. 그리고 확장해 가라

Write tests for methods that have the fewest dependencies first, and work your way up

 

예를 들어 Employee 모듈을 테스트 한다고 하자. 가장 먼저 Employee 모듈을 생성하는 코드부터 테스트를 작성하자. 왜냐하면 이 시나리오가 가장 낮은 외부 의존성을 가지기 때문이다. 이 시나리오가 성공한다면,  데이터베이스에 접근하는 테스트 코드를 추가하자.

 

데이터베이스에 Employee 정보를 가질려면 먼저 Employee 모듈을 생성하는 테스트 시나리오를 통과해야 한다. 만약 모듈을 생성하는 코드에 버그가 있다면 훨씬 빨리 발견할 수 있다.


프라이빗 메소드를 포함한 모든 메소드들은 가시범위에 상관없이 적절한 단위 테스트들을 작성해야 한다

All methods, regardless of visibility, should have appropriate unit tests


사실 이것은 논란거리이다. 소스의 코드의 핵심적인 부분은 그것이 프라이빗 메소드들이라도 반드시 테스트 되어져야 한다. 이 메소드들은 몇몇 클래스에서 호출 되어지는 어떤 결정적인 알고리즘을 가질 수 있다. 그러나 이것은 중요한 역활을 한다. 따라서 이 메소드들이 의도대로 동작하는 것을 확인해야한다.


각각의 단위 테스트 메소드는 정확히 하나의 검증구문을 가져야 한다

Aim for each unit test method to perform exactly one assertion


(번역자주 : 동의 하지 않음. 기능의 정확성을 확인하기 위해서는 복수의 검증구문이 필요할 수 있음.)


예상된 예외 사항을 테스트하는 단위 테스트 코드를 작성하라

Create unit tests that target exceptions

 

예상된 예외 사항을 검증하기 위한 테스트 케이스를 작성해야 하는 경우도 있다. 이때에는 아래와 같이 try/catch 구문으로 결과를 검증할려고 하지마라.


try{

          methodOccursDomainSpecificException();

          assertFail(Exception should be occurred);

}

catch(DomainSpecificException e){

}


대신 아래와 같이JUNIT이 제공하는 아래의 방법을 사용해라.


1.

@Test(expected=SomeDomainSpecificException.SubException.class)


가장 적합한 검증 구문을 사용하라

Use the most appropriate assertion methods


각 테스트 케이스에 사용할수 있는 많은 검증 구문이 있을 수 있다. 하지만 각 경우마다 이유와 의도에 맞게 가장 적합한 것을 사용하라.


검증 구문 파라미터들은 적합한 순서대로 배치하라

Put assertion parameters in the proper order

 

검증 구문은 대개 두 개의 파라미터를 취한다. 첫 번째 것은 테스트 결과가 패스할 때 기대하는 정상 값이고, 두 번째 것은 테스트 결과 값이다. 이 두 값들을 순서대로 작성하라. 이렇게 하면 테스트 실패시 에러 메세지를 통해 무엇이 잘못 되었는지 쉽게 확인할 수 있다.


테스트를 위한 코드는 제품 코드에서 분리되어야 한다

Ensure that test code is separated from production code

 

빌드 스크립트에서 테스트를 위한 코드는 실제 제품 코드와 같이 전달되지 않게 하라.


테스트 코드 내에서 아무것도 출력하지마라

Do not print anything out in unit tests

 

만약 테스트 케이스가 가이드 라인들에 따라 제대로 작성 되어졌다면 별도의 출력문이 필요하지 않다. 만약 출력문에 대한 필요성을 느낀다면 테스트 코드를 재검증 하라. 무엇인가 테스트 코드 중 잘못된 점이 있다.


정적 변수를 테스트 클래스에 사용하지 마라만약 사용했다면 각 테스트 케이스 실행시마다 재 초기화 해라

Do not use static members in a test class. If you have used then re-initialize for each test case


 이미 각 테스트들 간의 독립성에 대해 중요성을 언급했다. 따라서 절대 스트틱 변수들을 사용하지 마라. 만약 어쩔수 없이 사용해야 하는 경우가 발생한다면 각 테스트 케이스의 시작 전에 반드시 재 초기화 시켜라.


예외 발생시 단순히 테스트를 실패하기 위한 catc구문을 작성하지 마라. 

Do not write your own catch blocks that exist only to fail a test


 테스트 코드 내에 어떤 메소드가 예외를 발생시킬 수 있다면 단순히 테스트를 실패하기 위해 catch 구문을 사용하지 마라. 대신 throws 구문을 테스트 코드 선언시에 사용하라. 되도록이면 별도의 특정 예외 객체보다는 일반적이 Exception 예외 객체를 사용해라. 이 가이드 라인은 테스트 커버러지 증가에도 도움이 된다.


간접적인 테스트들에 의존하지 마라

Do not rely on indirect testing

 

하나의 테스트가 본래 의도된 시나리오 외 또 다른 시나리오도 테스트 한다고 가정하지마라. 이것은 혼란만 가져온다. 대신 또 다른 시나리오에 대한 추가적인 테스트 케이스를 작성하라.


단위 테스트를 자동으로 실행하게 빌드 스크립트를 작성해라

Integrate Testcases with build script

 

테스트 케이스들이 빌드 스크립트를 통해 자동적으로 실행되어지게 하라. 이것은 테스트 실행환경과 애플리케이션의 신뢰성을 높여준다.


단위 테스트들의 실행을 생략하지 마라

Do not skip unit tests

 

만약 단위 테스트의 코드가 적절하지 않다면 코드를 삭제해라. @Ignore 어노테이션을 사용하지마라. 소스코드의 부적절한 테스트 케이스를 포함하는건 아무런 도움이 되지 않는다.


테스트 결과를 XML 형태로 출력하라

Capture results using the XML formatter

 

즐거운 것은 좋은 것이다. 이 가이드 라인은 테스트 케이스 작성을 즐겁게 한다예를 들어 JUNIT을 빌드 스크립트와 통합시켜서 XML­로 테스트 결과를 출력 후 이것을 ­추가적인 포맷팅 작업을 통해 테스트 결과를 보기 좋게 바꿀 수 있다(번역자주 : JUNIT 프레임워크에서 제공하는 그린/레드UI 디스플레이를 의미하는듯 함.)



결론 

Conclusion


의심할 여지없이 단위 테스트는 소프트웨어 프로젝트 결과물의 품질을 월등히 향상시킬 수 있다. 소프트웨어 공학 관련 많은 학자들이 단위 테스트를 작성하는 것이 작성하지 않는 것보다 훨씬 효율적이라고 주장한다. 하지만 난 이 의견에 반대한다. 테스트 케이스들은 훌륭한 자산이다. 하지만 반대도 성립한다. 즉 형편없이 작성된 테스트 케이들은 프로젝트에 아무런 도움이 되지않는 골칫거리에 불과하다. 이러한 단위 테스트 코드의 품질은 개발자들이 단위 테스트의 목적과 철학을 얼마나 잘 이해하는지에 달려있는 것 같다.

 

독자 여러분이 그동안 저자가 서술한 가이드 라인을 잘 이해하고 그것을 잘 따른다면 다음 단위 테스트시 변화된 자신의 코드를 경험할 수 있을 것이다.


Happy Learning !!