Test-Driven C++ (CppCon 2020 - Phil Nash)

Test Driven Developement, Test Driven Design

TDD에서 가장 중요한 점은 ‘개발을 하면서 테스트를 만드는 것’이다.

무엇보다, ‘개발을 하기 전에 테스트를 먼저 작성한다‘.

이를 통해서 내가 개발을 하는 방법, 코드와 아키텍처를 디자인 하는 방식이 바뀌게 된다.

이는 기존의 ‘구현을 다 하고나서 제대로 만들었는지 테스트’ 하는 방법과 굉장히 다르다.

 

TDD Cycle

  1. 실패하는 테스트를 작성한다.
  2. 그 테스트가 패스할 수 있는 ‘최소한의 코드‘를 작성한다
  3. 리팩토링한다
  4. 원하는 기능을 다 만들었으면 끝낸다. 그게 아니라면 1로 돌아간다.

실패하는 테스트

테스트라는 말에는 여러가지 뜻이 내포된다.

첫번째로는 기능상의 테스트가 될 수 있다. GTest나 Catch2와 같은 프레임워크를 통해서 기능상의 테스트를 할 수 있다.

두번째로는 (많은 사람들이 간과하고 놓치지만) 컴파일러 빌드 테스트가 있다. 컴파일러가 코드를 빌드할 수 있어야 ‘빌드 가능한’ 코드임을 증명할 수 있다. 새로운 메소드나 클래스를 만들 때 이 테스트를 거친다면, 해당 메소드/클래스의 존재를 먼저 선언하고 개발을 시작하게 할 수 있다.

테스트가 패스할 수 있는 ‘최소한의 코드’

처음부터 좋은 코드를 짜려고 하면 고려해야할 점이 너무 많다. 아키텍처? Exception? 호환성?

하지만 테스트를 패스할 수 있는 ‘최소한의 코드’만 작성하면 1. 빠르게, 2. 테스트가 요구하는 기능을 충족할 수 있다. ‘기능 개발’이라는 부분에서 가장 중요한 부분에 집중할 수 있게 문제를 단순하게 만들어주는 것이다.

아키텍처, Exception, 호환성 같은 것들이 중요하지 않다는게 아니다. 이러한 점들은 이 다음 스텝에서 고친다.

리팩토링

이전 단계에서 기능 개발은 끝났으니, 이번 단계에서는 진짜 ‘클린 코드‘를 만드는 것이다.

가독성을 높이고, 아키텍처를 고려하면 된다.

테스트를 패스하는 코드는 이미 작성했으니, 우리는 이전 단계에서 작성한 테스트가 깨지지 않는 선에서 코드를 개선하면 된다.

TDD를 할 때 얻는 이점 중 하나는 ‘테스트하기 쉬운 코드‘를 유도하게 된다는 점인데, 이를 통해 기능의 모듈화나 인터페이스 분리와 같은 점들을 빠르게 고려해서 짜게 되는 점이 굉장히 큰 이점이다. 테스트하기 어려운 코드는 아무래도 좋은 디자인을 가지지 않기 떄문에, 고치게 된다.

리팩토링 단계가 매 TDD 사이클만다 일어난다고는 할 수 없지만, 해야한다면 제대로 해야한다는 점이 중요하다. 그렇지 않으면 더러운 구현 코드가 남게 되고, 코드 퀄리티를 높이기 위한 TDD는 실패하게 된다. 리팩토링을 잠깐 미룰 수는 있지만, 경험상 ‘아 그냥 그때 리팩토링을 할껄’ 하는 경우가 더 많다.

종합하면…

개발을 할 때 테스트는 implementation test로써 ‘개발의 진척도’를 나타내는 지표가 된다.

이후, 개발이 완료되었을 때는 regression test로써 ‘코드의 안정성’을 나타내는 지표가 된다.

TDD를 할 때 리팩토링 단계를 거침으로써 테스트하기 쉬운 코드를 짜야한다는 ‘디자인 압박‘을 받게 된다. 이 압박을 통해 테스트하기 좋은 코드를 작성하게 되고, 사용하기 쉬운 코드를 짜게 된다.

TDD를 통해서 개발하면 거의 항상 100%의 coverage를 달성할 수 있다 (i.e. 모든 코드가 테스트된다). 하지만 unit test만으로는 완벽한 안정성을 얻을 수 없기 때문에, 우리는 unit test를 넘어 그 다음 단계의 test들을 고려할 수 있게 된다. 이 점이 대단한 이유는, 기존의 개발->테스트작성 방식으로는 100%의 coverage를 얻는것도 어렵기 떄문에 그 다음 단계의 test를 고려하는 것도 거의 불가능하기 때문이다.