테스트는 얼마나 많이 해야 좋을까? 테스트는 얼마나 자주 해야 좋을까? 테스트는 얼마나 빨라야 좋을까? 테스트 범위는 얼마나 넓어야 좋을까? 그야 당연히, 최대한 많이, 최대한 자주, 최대한 빠르게, 최대한 범위가 넓게 테스트할 수록 좋다. 그러면 이 모든 조건을 동시에 만족할 수 있을까? 없다. 그러면 어떡해야 할까?
테스트 코드 비율
SQLite는 테스트 코드가 실행 코드에 비해 640배 길다. 라이브러리 코드는 14만줄인데, 테스트 코드는 9천만줄이다. 테스트를 정말 잘 만들었다. 신뢰할 수 있다.
하지만 유지보수도 그만큼 어려워진다. 1줄 수정하는데 640줄의 테스트 코드에 의존성이 생기는 셈이다. 단순히 테스트를 통과하면 되는게 아니라, 테스트 코드도 수정하거나 추가해야 할 수 있다. 테스트 코드가 없을 때보다 개발 속도가 641배 느려지는 셈이다.
물론 이것은 극단적인 경우다. SQLite는 데이터베이스라서 신뢰성이 매우 중요하고, 그래서 641배 느린 개발 속도를 감당할 수 있다. 보통은 이렇게 할 수 없다. 개발을 빨리 하려면 테스트 코드 비율을 낮춰야 한다. 그런데 그러면 신뢰성이 떨어질 것이다. 그럼 어떡해야 할까?
일단 현실을 받아들인다. 개발 속도와 신뢰성을 모두 만족할 순 없다. 테스트는 소중한 자원이니까 아껴써야 한다. 콘도 마리에처럼 가슴에 손을 얹고 설레는 기능에는 테스트를 많이 넣고, 아니면 적게 넣는다. 설레지 않는 테스트 코드는 과감하게 버린다. 테스트 코드 비율을 기능별로 0.5배, 1배, 2배, 3배 정도로 제한한다.
그리고 봄맞이 대청소를 하듯이, 테스트 코드도 주기적으로 리팩터링하고, 더 좋은 프레임워크를 적용한다. 테스트 파이프라인도 자동화하고, 테스트 툴을 도입해서 테스트를 쉽게 만든다. 이럴 시간이 없다면 주말이나 연휴 때 해야 할 것이다. 그냥 하면 보람이 없으니까, 얼마나 좋아졌는지 측정해서 매니저에게 자랑하고 연말 고과를 잘 받자.
테스트 주기와 속도
그럼 테스트는 얼마나 자주 해야 좋을까? 최대한 자주 할 수록 좋다. 책 “레거시 코드 활용 전략(Working Effectively with Legacy Code)“에서는 코드를 한 줄 고칠 때마다 테스트하라고 한다. 더 이상 자주 할 수 없을만큼 자주 하라고 한다. 그래야 버그를 일찍 발견해서 빨리 고칠 수 있다. 버그를 늦게 발견할수록 버그를 고치기 어려워지고 오래 걸린다.
근데 테스트가 오래 걸리면 개발 속도가 느려진다. 그러면 테스트는 얼마나 빨라야 할까? 저 책에서는 테스트를 1초 안에 끝내라고 한다. 그래야 테스트를 자주 돌려도 부담이 없다고 한다. 테스트가 1초 걸리면 1초마다 테스트할 수 있고, 테스트가 1분 걸리면 1분마다 테스트할 수 있다.
아니, 테스트를 어떻게 1초에 끝내나? 테스트 범위를 좁히면 된다. 펑션 하나를 테스트하고, 그 앞뒤로 Mock을 만든다. 아주 단순한 테스트라도 좋다. 그게 끝나면 테스트 범위를 넓히고 그 앞뒤로 Mock을 만든다.
테스트 범위
그럼 테스트 범위는 어디까지 넓혀야 하나? 그야 최대한 넓힐수록 좋다. 유닛 테스트, 인테그레이션 테스트, 시스템 테스트를 넘어서 CDC(Consumer Driven Contracts)를 이용해서 마이크로 서비스 간의 의존성까지 테스트하면 좋다.
이런 테스트들을 CI/CD 파이프라인에 엮어서, git push 할때마다 테스트하면 좋다. 그런데 테스트가 점점 많아지면 테스트가 길어지니까, 빌드 시간보다 테스트 시간이 길어진다 싶으면 빌드와 테스트를 분리한다. 그래서 테스트가 실패하면 팀 멤버들에게 노티를 띄워서 고치라고 한다.
그리고 테스트 커버리지를 측정하는 정적 도구를 활용할 수 있다. 테스트 커버리지가 처음으로 80%를 넘으면 CTO가 점심 회식을 쏠 수 있다. 물론 그러면 테스트 코드가 너무 많아지니까 기능별로 90%, 80%로 차등화해서 제한한다.
테스터
테스트 코드는 누가 개발해야 할까? 원래 코드를 짠 개발자가 기능을 제일 잘 아니까 테스트 코드도 잘 만들 것 같고, 별도의 테스트 엔지니어가 제 3자의 입장에서 다른 시각으로 테스트 코드를 잘 만들 것도 같다. 그러면 둘 다 하는게 좋겠다. 근데 그러면 인적 자원을 많이 소모하니까 적절히 해야 한다.
테스트는 누가 하는게 좋을까? 개발자가 하고, 전문 테스터가 하고, 다른 부서원이 하고, 고객이 하고, 많이 할 수록 좋다. 그래야 개발자가 놓친 부분, 테스터가 놓친 부분, 다른 부서원이 놓친 부분을 더 찾을 수 있다. 그런데 그러면 인적 자원을 많이 소모하니까 적절히 해야 한다.
적절히 하는 방법은 위에서도 말했듯이, 설레지 않으면 버려라. 설레는 기능에는 많은 자원을 투자하고, 설레지 않는 기능에는 적은 자원을 투자한다. 이렇게 비용 대비 효율을 높이고, 다른 한 편으로는 테스트의 효율성을 높일 수 있도록 자동화, 체계화 등을 한다.
테스트 주도 개발(TDD)
그럼 테스트를 먼저 만드는게 좋을까? 잘 모르겠다. 테스트에 너무 의존할 수 있기 때문이다.
완벽한 테스트는 만들기 정말 어렵다. 보통 테스트를 그렇게까지 잘 만들지 못한다. 테스트를 허술하게 만들거나 잘못 만들었는데, 그 테스트에 의존해서 개발하다보면 개발도 잘못할 수 있다. 테스트에 현혹되면 안된다. 아무리 테스트가 중요해도, 기능 개발 자체가 더 중요하다.
그리고 나는 좀 산만하게 개발하는 편이다. 아주 작은 펑션을 만들고, 그것의 테스트를 만들고, 그게 잘 돌아가면 펑션을 조금 키우고, 테스트도 따라서 조금 키우고… 이렇게 반복해서 원하는 크기까지 키운다. 굳이 순서를 따지지 말고 개발과 테스트를 반복해도 된다.
그리고 테스트가 쉬운 코드가 좋은 코드일까? 대체로 그렇겠지만 꼭 그렇진 않다. 테스트 프레임워크나 테스트 툴이 훌륭하지 않을 수 있다. 테스트를 효율적으로 만들다가 실제 코드를 비효율적으로 만들 수 있다. 그러면 안된다. 그 비효율적인 정도가 얼마 안되거나 코드의 가독성을 높여준다면 괜찮겠지만, 1% 확률로 1000배 느려진다면 심각해진다. 테스트에 너무 집착하면 안된다.
결론
테스트는 중요하지만 비싸다. 우리는 소프트웨어 개발을 싸게 해야 한다. 우리의 소프트웨어를 가성비 맛집으로 만들어야 한다. 테스트를 많이, 자주, 빨리, 넓게, 다양하게 하면서 자원을 적게 써야 한다. 여기서부터 모든 문제가 시작된다.
테스트 코드 비율을 3배, 테스트 주기는 1줄마다, 테스트 속도는 1초 이내, 테스트 커버리지는 80%, 테스트 주체는 개발자와 테스터, 이런 수치들을 각 기능별로 차등화해서 관리한다. 테스트 자동화, 테스트 파이프라인 구축, 테스트 코드 리팩터링 등으로 테스트 코드를 작성하는 부담을 덜어준다. 이렇게 전부 다 잘 하면 된다.