과거 대다수의 프로그래머들은 TDD(Test Driven Development)라는 개념을 몰랐으며 단위 테스트란 자기 프로그램이 '돌아간다'는 사실만을 확인하는 일회성 코드에 불과했다. 지금에 와서는 애자일과 TDD 덕택에 단위 테스트를 자동화하는 프로그래머들이 많이 늘어났고 계속해서 늘어나는 추세다. 이번 장을 통해 테스트 케이스를 잘 작성하는 방법에 대해 알아보자.
TDD 법칙 세 가지
TDD는 실제 코드를 짜기 전에 단위 테스트를 작성하라고 요구할것이다. 다음 세 가지 법칙을 살펴보자.
- 실패하는 단위 테스트를 작성할 때까지 실제 코드를 작성하지 않는다.
- 컴파일은 실패하지 않으면서 실행이 실패하는 정도로만 단위 테스트를 작성한다.
- 현재 실패하는 테스트를 통과할 정도로만 실제 코드를 작성한다.
위 세 가지 규칙을 따른다면 실제 코드와 테스트 코드가 함께 묶여서 나올것이다. 몇 초 주기라고 말하기 애매하지만 테스트 코드가 잘 작성되었다는 가정하에 실제 코드는 금방 작성된다. 이렇게 일한다면 매일 수십 개, 매달 수백 개, 매년 수천 개에 달하는 테스트 케이스가 나온다. 모든 실제 코드를 사실상 전부 테스트하는 테스트 케이스가 나오는 것이다. 그러나 실제 코드와 맞먹을 정도로 방대한 테스트 코드는 관리 문제를 유발한다.
깨끗한 테스트 코드 유지하기
과거 책의 글쓴이는 실제 코드와 동일한 품질 기준을 적용하지 않아야 한다고 명시적으로 결정된 팀을 코치하였었다고 한다. 팀원들에게 단위 테스트의 규칙을 깨도 좋다는 허가를 내렸고 지저분해도 빨리 개발하자는 것이 주제였다고 한다. 변수이름, 간결성, 잘 설계하는것 모두를 버리고 그저 돌아가기만 하면 되는 테스트 코드를 작성하면 되는 것이었다.
잘 작성하지 않은 테스트코드는 당장은 편할지 모르나 추후 많은 문제가 드러난다. 실제 코드가 발전하면 테스트 코드도 변해야 한다. 그러나 테스트 코드가 지저분하면 변경하기 힘들고 실제 코드를 짜는 시간보다 테스트 케이스를 작성하는 시간이 더 오래 걸리는 순간들이 많아진다. 기존 테스트 케이스가 실패하기 시작하면, 그동안 지저분하게 짜왔던 코드로 인해 실패하는 테스트 케이스들이 점점 늘어나고 어느 순간 테스트 케이스는 부담이 되어버린다.
새 버전을 출시할 때마다 테스트 케이스를 유지하고 보수하는 비용이 점점 커지며 큰 불만사항으로 자리 잡게 된다. 결국 테스트 코드를 폐기해야 하는 상황에 처하게 된다. 그러나 적절한 테스트 케이스가 없다면 개발자는 자신이 수정한 코드가 제대로 도는지 확인할 방법이 없다. 마땅히 검증할 방법이 없는 것이다.
결국에는 실패하는 테스트 케이스 때문에 검증하기를 포기하고 시스템의 결함률은 높아지는 악순환이 벌어진다.
테스트 케이스가 제공하는 장점들(유연성, 유지보수성, 재사용성)
테스트 코드를 깨끗하게 유지해야 하는 이유들을 더 살펴보자. 테스트케이스는 코드에 유연성, 유지보수성, 재사용성을 제공하는 버팀목이 되어준다. 수정 요청사항이 들어왔을 때 또는 개선해야 할 부분들에 있어 기존에 잘 돌아가고 있는 프로그램을 수정하기란 만만치 않다. 수정 시 어느 곳에서 예상치 못한 버그가 발생할지 알 수 없기 때문이다. 그러나 테스트 케이스가 존재한다면 수정 후 테스트가 간단해지며, 미리 만들어둔 예외 사항들을 모두 통과한다면 이 코드가 안전하게 돌아간다는 확신을 얻을 수 있을 것이다.
따라서 테스트 케이스를 작성하는 것을 권장하며, 작성한다면 실제코드와 같이 작성하고 깨끗하게 유지하여야 한다. 테스트 코드가 실제 코드와 다르고 지저분하다면 결국에는 테스트 코드를 잃어버리고 실제 코드도 망가지게 된다.
깨끗한 테스트 코드
깨끗한 테스트 코드를 위해서는 가독성이 가장 중요하다. 명료성, 단순성, 풍부한 표현력이 필요하다. 테스트 코드는 최소의 표현으로 많은 것을 나타내야 한다.
도메인에 특화된 테스트 언어
도메인에 특화된 언어(DSL)로 테스트 코드를 구현하는 기법이다. 시스템 조작 API를 사용할 때에 무슨 역할을 하는지 쉽게 알기 어려울 때가 있다. 이때에 API를 직접 호출해 사용하는 대신 API위에다 필요한 대로 함수와 유틸리티를 구현한 후 그 함수와 유틸리티를 사용하므로 테스트 코드에서 사용하는 특수 API가 된다.
DSL 이란?
도메인 특화 언어는 관련 특정 분야에 최적화된 프로그래밍 언어입니다.
필자가 이해하기로는 이 책에서 말하는 도메인 언어로 테스트 코드를 구현한다는 것은 테스트를 구현하는 당사자와 나중에 테스트 코드를 읽게 될 독자를 도울 수 있도록 "API의 코드를 감싸 테스트 코드로 이름을 이해하기 쉽게 최적화하는 방법"을 말하는 것 같다.(필자가 이해한 것이므로 틀릴 가능성이 있다.)
이중 표준
테스트 API에서 적용하는 표준은 실제 코드에 적용하는 표준과는 확실히 다르다. 단순하고, 간결하고, 표현력이 풍부해야 하지만 실제 코드만큼 효율적일 필요는 없다. 예를 들어 여러 기능을 테스트해야 할 때 하나의 메서드 넣어 해결할 수 있지만 테스트 코드인 만큼 모두 분리하여 더 보기 쉽게 만들어도 괜찮다는 이야기이다.
또한 문자열을 출력할 때에도 String을 사용하여 출력하는 것보다 효율만 따지자면 StringBuffer를 사용하는 것이 더 효율적이지만 테스트 환경에서는 크게 무리가 아니라면 String을 사용해도 좋다. StringBuffer 사용 시 코드가 보기 흉해지기 때문이다. 실제 환경에서도 StringBuffer를 쓰지 않아서 치르는 대가가 미미한 경우가 대부분이므로 크게 무리가 아니라면 String을 사용해도 괜찮다.
테스트 당 assert 하나
Junit으로 테스트 코드를 짤 때는 함수마다 assert 문을 단 하나만 사용해야 한다고 주장하는 이들이 있다. 꽤나 귀찮은 규칙이라 여겨질 수 있으나 assert문이 하나인 함수는 결론이 하나라서 코드를 이해하기가 쉽고 빠르다. 물론 이것이 강제되는 규칙은 아니다. 그러나 단일 assert문이라는 규칙은 꽤나 훌륭한 규칙이라고 생각된다.
테스트 당 개념 하나
테스트 함수마다 한 개념만 테스트하기를 권장한다. 만약 개념이 세 개가 존재한다면 세 개의 테스트로 쪼개는 것이 마땅하다. 세 개념을 한 함수로 몰아넣는다면 독자는 각 절이 거기에 존재하는 모든 이유를 이해해야 한다.
예를 들어보자
- 에어컨은 status 값이 true 일시 전원이 켜진다. 날씨가 겨울일 때에는 온풍기로 가동된다.
- 에어컨은 status 값이 true 일시 전원이 켜진다. 날씨가 여름일 때에는 냉풍기로 가동된다.
- 에어컨은 각 계절에 따라 해당 공간이 적정 온도를 유지하고 있다면 가동되지 않는다.
위와 같은 경우는 코드를 이해하는데 시간이 더 걸릴 것이다. 테스트 함수 하나는 개념 하나만 테스트하는 것이 가장 바람직하다.
F.I.R.S.T
깨끗한 테스트는 다섯 가지의 규칙을 따르는데, 각 규칙을 살펴보자
빠르게(Fast)
테스트는 빨라야 한다. 테스트가 빨리 돌지 않는다면 자주 돌릴 엄두를 내지 못한다. 자주 돌리지 않으면 초반에 문제를 잡아내지 못한다. 코드를 마음껏 정리하지도 못하고 결국 코드의 품질이 나빠질 것이다.
독립적으로(Independent)
각 테스트는 서로 의존하면 안 된다. 한 테스트가 다음 테스트의 실행 환경을 준비해서는 안된다. 각 테스트는 독립적이어야 하고 어떠한 순서도 실행돼도 문제가 없어야 한다. 테스트가 서로에게 의존할 경우 하나의 테스트 실패 시 모든 테스트가 실패하므로 원인을 진단하기가 어려워지며 후반 테스트가 찾아내야 할 결함을 놓칠 가능성이 생긴다.
반복가능하게(Repeatable)
테스트는 어떤 호나경에서도 반복 가능해야 한다. 실제 환경, QA 환경, 버스를 타고 집으로 가는 길에 사용하는 노트북에서도 실행할 수 있어야 한다. 테스트가 돌아가지 않는 환경이 있다면 환경이 지원되지 않기에 수행하지 못했다는 실패를 둘러댈 이유가 생기며 테스트를 수행하지 못할 상황이 생긴다.(데이터 베이스가 없이 실행될 수 없는 테스트 환경을 예로 들 수 있을 것이다. 이때에는 인메모리 디비를 사용할 수 있어야 할 것)
자가검증하는(Self-Validating)
테스트는 bool 값으로 결과를 내야 한다. 성공 아니면 실패다. 통과 여부를 알기 위해서 로그파일을 읽게 해서는 안된다. 테스트가 스스로 성공과 실패를 가늠하지 않는다면 판단은 주관적이 되며 지루한 수작업 평가가 필요해진다.
적시에(Timely)
테스트는 적시에 작성해야 한다. 단위 테스트는 테스트하려는 실제 코드를 구현하기 직전에 구현한다. 실제 코드를 구현한 다음에 테스트 코드를 만들면 실제 코드가 테스트하기 어렵다는 사실을 발견할지도 모른다. 또한 테스트가 불가능하도록 실제 코드를 설계할지도 모른다.
결론
테스트 코드는 실제 코드만큼이나 프로젝트 건강에 중요하다. 필자도 실제로 현업에서 프로젝트의 유지보수를 진행하며 느꼈다. 유지보수를 위해 에러를 찾고 코드를 변경해야 하는데 테스트 코드가 없다면 검증하기가 굉장히 번거로워져 유연하게 변경할 수 없기 때문이다.
테스트 코드는 실제 코드의 유연성, 유지보수성, 재사용성을 보존하고 강화하는 효과가 있다. 그러므로 테스트 코드는 지속적으로 깨끗하게 관리하자. 표현력을 높이고 간결하게 정리하자.
'Read Book > CleanCode' 카테고리의 다른 글
CleanCode 8장 경계 (0) | 2022.11.09 |
---|---|
CleanCode 7장 오류 처리 (0) | 2022.11.06 |
CleanCode 6장 객체와 자료구조 (0) | 2022.10.05 |
CleanCode 5장 형식 맞추기 (0) | 2022.09.24 |
CleanCode 4장 주석 (3) | 2022.09.19 |