brunch

You can make anything
by writing

C.S.Lewis

by 김지영입니다 Apr 24. 2016

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

한글화 프로젝트

한글화 프로젝트는 지앤선과 국내 커뮤니티들이 함께 해외의 다양한 주제의 유익한 문서 등을 번역하여 영어를 읽는데 어려움이 있는 여러 사람에게 도움을 드리고자 진행 중인 프로젝트이다.


이 글은 KSUG의 최한뫼 님이 번역을 진행해주신 것으로 지앤선의 블로그를 통해 공개되었던 것을 재정리하여 브런치로 옮겨왔다. 원본은 여기에서 확인할 수 있다.



단위 테스트 활용 방법: 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이 제공하는 아래의 방법을 사용해라.     


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

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

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


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

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!!

브런치는 최신 브라우저에 최적화 되어있습니다. IE chrome safari