Share

Unittest is dead

  • 2020년 09월 02일

TDD는 테스팅을 깊은 수준으로 사고하는 방법을 가르쳐주는 훌륭한 도구 였다. 하지만 근본주의자 같은 TDD 적용 방식은 비효율적이다. 이제 나는 테스트를 먼저 개발하지 않는다. 테스트 우선 접근은 제한적인 시스템 디자인 도구로써 여전히 사용하겠지만 더 이상 반드시 TDD 방식을 따르겠다는 생각은 없어졌다. 하지만 여전히 Q/A 부담을 덜어주는 도구로서 가치가 있다. 오픈 시점에 중요한 로직의 코드들에 대한 유닛테스트를 만들어두며 그 이후부터 로직이 깨지는 상황을 막기에 좋다. 테스트 코드도 의존성이고 로직이다. 내부 Q/A 리스트에 매칭되는 유닛테스트 정도로 최소한을 유지하는 것이 유지보수에도 좋다.


TDD의 단점

  • 테스트 우선 방식은 객체들이 얽혀 있는 과도하게 복잡한 구조를 만들기 쉽다. 무섭고 거대한 구조로 가려는 경향이 크다.
    • 쓸데없는 인터페이스가 늘어나느점이 단점? 하지만 확장을 위한 공간 측면에서 나는 다소 지지하는 편이다.
  • 목업 객체들은 통해 테스트하는 것은 실제 환경과 틀리다.

어떻게 해야하는가?

  • 유닛테스트보다 시스템 테스트의 우선순위를 높힌다. 세세한것보다는 코어 시스템의 동작여부를 테스트하는 것이 좋다. 시스템 테스트는 테스트코드와 로직코드를 분리해서 제작해도 된다.
  • 유닛테스트를 만들지 않고 로직코드안에 테스트 역할을 하는 코드를 사용한다.
    • 계약에 의한 설계(Design By Contract)를 적용해 문제가 생겼을때 바로 검출이 가능하도록 한다.(assert, invariant(불변표명검사)). unittest코드 자체가 이미 DBC를 처리하는 하나의 방법중 하나이다. C#의 Property등을 이용해서 Range를 제한하는 등의 방법도 있을 것같다. 핵심은 유닛테스트가 수행했던 코드 품질 검사 기능을 로직코드와 합쳐서 사용할 수 있도록 하는 것이다

Design By Contract

명령형 언어(imperative language)의 단점은 부수 효과(side effect)로 인한 결과값이 변경될 수 있다는 점에 있다. 함수형 언어(functional language)를 사용할 수도 있지만 프로그래밍 언어의 주류는 C++,Java와 같은 명령형 언어들이다. 해당 언어들의 부수효과를 방지하기 위한 기법중 하나가 계약에 의한 설계이다.

계약에 의한 설계(DBC)는 LiskovSubstitutionPrinciple을 강제하는 테크닉이다. DBC를 사용하면 어떤 클래스의 제작자는 그 클래스의 계약 사항을 명시적으로 정한다. (사전조건/사후조건/클래스 불변 표명). Effiel과 같은 특정언어는 이것을 직접적으로 지원한다. DBC를 적용하는 방법은 여러가지가 있다.

Assert

배포에 포함되지 않는 assert를 사용하여 DBC를 적용할 수 있다.

  • 사전 조건
void set_exp(int exp) {
        assert(exp >= 0);
        ...
        ...
}
  • 사후 조건
void calculate_level() {
        ...
        ...
        assert(level<= MAX_EXP);
        return level;
}
  • 클래스 불변 표명. 해당 객체가 반드시 지켜야할 조건을 기술한다. 해당 조건을 검사하는 함수는 invariant라는 단어가 들어가도록 한다.
// 불변 표명 검사
bool invariant() {
        ....
        ...
        if (condition)
                  return false;
        return true;            
}
// 불변 표명 적용
void process_skills() {
        assert(invariant());
        ....
        ....
        assert(invariant());
}

Unit test

코드안에서 직접 assert를 호출하지 못하는 경우 유닛테스트를 통행 DBC를 적용할 수 있다.

Player p = new Player();
assert(new_exp >= 0);
p.set_exp(new_exp);
int level = p.calculate_level();
assert(level <= MAX_EXP);
assert(invariant_conditions());
p.process_skills();
assert(invariant_conditions());

Documentation

assert와 유닛테스트로 적용할 수 없다면 doxygen등으로 코드를 문서화할때 사전-사후-불변 조건을 기술한다.


참조

https://sangwook.github.io/2014/04/25/tdd-is-dead-long-live-testing.html

http://en.wikipedia.org/wiki/Design_by_contract

http://aeternum.egloos.com/2388357

http://blog.naver.com/PostView.nhn?blogId=hamlet21&logNo=130007668631&widgetTypeCall=true
http://www.eventhelix.com/realtimemantra/object_oriented/design_by_contract.htm#.Uk0-3NK8BaY – DBC in C++

0
Would love your thoughts, please comment.x
()
x