주요 내용
- 자동 테스트는 소프트웨어를 변경할 수 있게 해주는 토대
- 테스트를 확장하려면 반드시 자동화해야 함
- 우수한 테스트 커버리지를 위해서는 균형 잡힌 test suite 필요
- 조직의 테스트 문화를 바꾸는 데는 시간이 필요
소프트웨어 시스템의 복잡도에 대응하기 위해 테스트 코드의 중요성이 강조되고 있는 요즘, 구글의 사례를 통해 어떻게 테스트 코드를 관리하고 테스트 문화를 도입하게 되었는지 알아보는 것이 이번 챕터의 목표 입니다. 테스트는 단순히 버그를 잡기 위해서 뿐만 아니라 소프트웨어가 안전하게 변화할 수 있도록 지원, 시스템의 설계 개선 등 여러 이유로 꼭 필요합니다.
테스트 코드가 주는 혜택은 다음과 같습니다.
가장 단순한 테스트는 다음의 요소들이라고 정의할 수 있습니다.
문제가 터진 후에야 테스트를 고민해서는 안되며, 제품 결함 해결을 프로그래머의 능력에만 의존해서는 안됩니다. 그러므로 자동 테스트는 필수입니다. 좋은 자동 테스트 문화는 QA뿐아니라 모두가 테스트를 작성하고 공유하도록 장려하는 것입니다. 또한 테스트를 정기적으로 실행해야 하며, 테스트가 실패하면 바로 조치하도록 권장해야합니다.
기본적인 자동 테스트 = 테스트 작성, 테스트 수행, 실패한 테스트에 대한 조치
마지막으로 테스트는 엔지니어에게 신뢰를 줄 때만 가치가 있음을 명심해야 합니다.
모든 test-case에는 size와 scope이라는 두 가지 독립된 요소가 있습니다.
size: test case 하나를 실행하는 데 필요한 자원 (ex. 메모리, 프로세스, 시간, ...)
scope: 검증하려는 특정한 코드 경로
구글에서는 모든 테스트를 크기 기준으로 분류하며, 엔지니어들에게 주어진 기능 조각을 검사하는 가능한 한 작은 테스트를 작성하라고 독려합니다. 테스트의 크기를 가늠하는 기준은 어떻게 동작하고, 무엇을 하고, 얼마나 많은 자원을 소비하는지로 평가합니다. 또한 구글은 ‘단위/통합 테스트’ 대신 작은 테스트, 중간 테스트, 큰 테스트로 나누어 사용합니다. test suite에 바라는 품질은 속도와 결정성이기 때문입니다.
- 작은 테스트
- 제약이 가장 엄격
- 단 하나의 프로세스에서 실행 되어야 함
- 서버를 두고 독립된 테스트 프로세스에 연결해 수행하는 방식도 허용하지 않음
- DB와 같은 제3의 프로그램을 수행해서도 안됨
- 네트워크와 디스트에 접근할 수 없으므로 sleep, I/O 같은 블로킹 호출을 사용해서도 안됨
- 중간 테스트
- 여러 프로세스와 스레드를 활용할 수 있음
- 로컬 호스트로의 네트워크 호출 같은 블로킹 호출도 이용 가능
- DB를 실행하거나 웹 UI와 서버 코드의 조합도 테스트 가능
- 유연성이 커져 테스트는 느려지고 비결정적이 될 수 있음
- 외부 요인이 개입되므로 성능과 결정성을 온전히 스스로가 보장할 수 없음
- 큰 테스트
- 테스트와 대상 시스템이 여러 대의 기기를 활용할 수 있음
- 단일 기기에서 구동할 때보다 느려지거나 비결정성이 커질 가능성이 높음
- 몇가지 용도에 한정해서 활용
- 시스템 종단간(end-to-end) 테스트: 설정을 검증하는 것이 주된 목적
- 테스트 대역을 사용하는 것이 불가능한 레거시 컴포넌트를 테스트
- 테스트 크기와 무관한 공통 특성
- 모든 테스트는 밀폐 되어야 함
- 즉, setup, execute, tear down 하는 데 필요한 모든 정보를 담고 있어야 함
- 테스트 수행 순서 같은 외부 환경에 관해서는 가능한 아무것도 가정하지 않아야 함
- 테스트에서는 조건문이나 순환문 같은 제어문을 쓰지 않는 것이 좋음
테스트 범위란 주어진 테스트가 얼마나 많은 코드를 검증하느냐를 뜻합니다.
- 좁은 범위 테스트
- ex) 단위테스트
- 독립된 클래스나 메서드 같이 코드 베이스 중 일부 로직을 검증하도록 설계
- 중간 범위 테스트
- ex) 통합 테스트
- 컴포넌트 사이의 상호작용을 검증하도록 설계
- 넓은 범위 테스트
- ex) 기능 테스트, 종단간 테스트, 시스템 테스트
- 시스템의 서로 다른 부분들 사이의 상호작용이나 클래스나 메서드 하나만 실행할 때는 괜찮다가 여럿을 조합해 실행하면 나타나는 예기치 못한 동작을 검증하도록 설계
코드 커버리지는 어느 테스트가 기능 코드의 어느 라인을 실행하는지를 측정하는 수단입니다. 코드 버커리지를 테스트 품질을 파악하는 지표로 사용하는 것을 권장하지 않습니다. 왜냐하면 커버리지 자체가 목표가 될 수 있기 때문입니다. 예를 들어 80%라는 목표치를 정해두면 그 이상해야 할 동기를 잃게 됩니다. 그러므로 검사해야 할 행위에 집중합시다. 참고로 큰 테스트는 커버리지 인플레이션을 일으키므로 작은 테스트에서만 측정해야한다.
구글의 개발 환경을 살펴보면 모든 코드를 monorepo로 관리합니다. 그리고 해당 코드는 모든 엔지니어가 재사용할 수 있습니다. 코드베이스를 열어두면 공동 소유 의식이 생기는 이점이 있습니다. 또한 리포지터리 브랜치를 사용하는 팀이 거의 없습니다. 모든 변경이 레포지터리 헤드에 직접 커밋되어 변경 즉시 모두가 볼 수 있습니다. 나아가 모든 소프트웨어는 테스트 인프라가 검증한 가장 최신 커밋까지 반영해 빌드 됩니다. 구글의 규모와 모노리포 정책 등을 생각하면 코드가 수정되는 것은 당연한 일입니다. 그러므로 테스트의 중요성이 더 대두되었습니다.
깨지기 쉬운 테스트 (brittle test), 즉 예상 결과를 너무 세세하게 표현하거나 광범위하고 복잡한 상용구가 덕지덕지한 테스트는 지양해야합니다. 이런 테스트를 만드는 주범으로 모의 객체 오용을 들 수 있습니다. 또한 테스트 스위트가 커지면 수행시간이 길어진다는 점도 문제입니다.
많이 사용하는 유틸리티에서 sleep(), setTimeout() 같은 함수를 호출하면 유휴 시간이 몇 분까지 늘어날 수 있음에 주의해야합니다. 이런 방식보다는 수 마이크로초 정도의 짧은 주기로 상태가 달라졌는지를 폴링하는 전략이 좋습니다.
거대한 테스트 스위트를 잘 관리하는 비결은 테스트를 존중하는 문화입니다.
구글은 테스트 문화를 정착시키기 위해서 Testing Grouplet이라 불린 자원봉사단이 환경을 적극적으로 개선했습니다.
첫째로, 신규 입사자들에게 테스트 코드 작성이 원래 구글의 관례였던 것 처럼 오리엔테이션을 진행했습니다. 그런 교육을 받은 신규 입사자들은 기존 엔지니어들에게 테스트 코드 문화를 전파했습니다.
둘째로, 테스트 인증이라는 인증 프로그램을 만들었습니다. 테스트 인증의 목적은 각 팀이 자신의 테스트 프로세스 수준(성숙도)을 알게 하고 한 단계 올라서기 위한 지침을 제공하는 것이었습니다.
셋째로, Testing on the Toilet(TotT)을 했습니다. 화장실 변기 앞에 전단지를 붙여 회사 전반의 테스트에 대한 인식을 적극적으로 높이는 목적의 활동입니다. 현재 이 TotT는 구글의 문화가 되었고 이에 대한 글들은 https://testing.googleblog.com/search/label/TotT 여기에 정리되어 있습니다.
물론 모든 종류의 테스트를 다 자동화 할 수는 없습니다.
예를 들어 검색 결과의 품질을 테스트 하려면 사람의 판단이 개입되어야 합니다.