이펙티브 소프트웨어 테스팅 - 마우리시오 아니시
최근 프론트엔드 테스트에 관심이 생기게 되면서 주변 분들에게 추천받은 책입니다. 개인적인 하반기 목표로 팀 프로젝트에 유닛, 통합 테스트를 도입하려 하는 데 도움이 될 것 같아 읽어봤습니다.
테스트 케이스 도출, 상황에 따른 테스트 기법, 코드 커버리지와 같은 효율적이고 체계적인 테스트 작성법에 대해 서술합니다. 저의 경우 매 스프린트마다 피쳐들을 개발하는데 치여 테스트 코드를 작성할 엄두가 안 났었는데, 테스트 코드에 대해 얼마큼 시간을 할애할 수 있을지에 대한 지표가 될 것 같습니다.
다양한 테스트 기법들을 예제와 함께 소개합니다. 하나의 기법만으로 테스트하는 것을 지양합니다. 평소 e2e 테스트 위주로 작성해 단위, 통합 테스트에 대해 자신이 있지는 않았는데 작은 범위 단계에서 코드 커버리지의 이해와 평소에 생각하지 못했던 다양한 케이스들에 대한 예시들을 익힐 수 있었습니다.
TDD에 관한 내용은 짧게 있습니다. 무조건 적인 TDD보다 상황에 따라, 테스트 코드를 작성하는 개발자의 역량에 맞춰 작성하는 법을 제시합니다. 코드 작성의 연습을 강조합니다. 연습을 통해 코드를 작성하면서 본인에게 가장 효율적인 방법을 찾는 것을 강조합니다.
저자가 유닛 테스트를 선호해서 인지 유닛 테스트에 대한 내용이 많습니다.
책의 전반적인 내용들을 요약하는 1장을 정리해 봤습니다.
개발자를 위한 효율적이고 체계적인 소프트웨어 테스트는 위의 5가지 경우의 테스트를 적절히 사용해야 한다.
- 기능 개발의 요구사항을 받아 분석하고 코드를 작성한다.
- 짧은 TDD 과정을 반복한다. 이 과정에서 피드백을 받고, 리팩터링을 진행한다.
- 요구사항이 커지게 되면 쪼개 서로 어울려 전체 기능을 구성한다.
- 요구사항을 충족한다고 생각하면 테스트를 작성한다. 새로 만든 각 단위를 테스트한다. (도메인, 경계, 구조적 테스트)
- 대규모 테스트가 필요하다면 진행한다. (통합, 시스템 테스트) 이때는 큰 시스템의 큰 부분만 살펴본다.
- 다양한 기법으로 테스트 도구를 만들었다면 지능형 테스트 도구를 써 사람이 못 찾는 케이스를 찾는다. (테스트 케이스 생성, 돌연변이 테스트, 정적 분석..)
예시일 뿐 따를 필요는 없고 자신에게 가장 적합하고 생산적인 방법을 찾아야 한다. 과정이 선형적으로 보이지만, 필요하다면 언제든지 처음으로 돌아가야 한다.
개발과 테스트를 분리하자. 개발하면서 생각나는 테스트 케이스는 어디엔가 기록한다.
개발을 마치고 나서 테스트를 작성할 땐 일반적인 테스트를 작성하고, 추가로 창의성과 도메인 지식을 이용해 테스트를 작성한다.
테스트 코드를 작성하는 데는 비용이 많이 든다. 하지만 버그를 제때 찾지 못해 발생한 장애의 손실이 더 크다.
테스트 케이스 작성의 비용은 개발자의 역량이 중요하다. 작성 비용이 덜 들도록 연습을 통해 익숙해져야 한다.
테스트 케이스를 설계하는 것과 실행하는 것은 별개다.
테스트 프레임워크는 실행하고, 보고서와 실패 사유 등을 만들어 주는 것이 전부다.
모든 걸 테스트하는 것은 불가능하다. 따라서 효율적인 테스트를 해야 한다.
우리의 목표는 비용을 최소화하고 최대한 많은 버그를 찾는 것이다. 이것을 위해 테스트를 언제 그만둘 건지에 대해 고민해야 한다.
다양한 테스트 기법을 사용하자. 하나의 테스트 기법으로만 테스트를 한다면 그 테스트 수준 외의 버그는 찾을 수가 없다.
가령 머케팅 모듈보다 결제 모듈에 많은 버그가 발생한다. 이런 곳에 우선순위를 더 두어 엄격한 테스트를 진행할 수 있도록 한다.
테스트가 얼마나 많든 간에 소프트웨어의 버그가 100% 존재하지 않는다는 것을 보장하지 않는다. 테스트는 시스템이 우리가 기대하는 대로 동작하는지를 검증하는 케이스만을 보장한다.
어떤 소프트웨어를 테스트하냐에 따라 테스트 기법이 달라질 수 있다.
오류 없이 동작하지만 사용자에게 쓸모없는 소프트웨어 시스템은 좋은 시스템이 아니다.
검증은 시스템이 제대로 되어 있는가에 관한 것 (오류 없이 동작하는가), 유효성 검사 (쓸모 있는가) 는 올바른 시스템을 가지는 방법에 관한 것이다.
검증과 유효성 검사는 함께 진행해야 한다. 유효성 검사를 진행하면서 기획자가 생각하지 못한 코너 케이스를 발현할 수도 있다.
테스트 수준에 따라 단위 테스트, 통합 테스트, 시스템 테스트가 있다.
테스트 크기에 따른 정의
작은 테스트: 단일 프로세스에서 실행할 수 있는 테스트. 빠르며 불안정하지 않다.
중간 크기 테스트: 여러 프로세스에 걸쳐 실행될 수 있고, 스레드를 사용할 수 있으며 로컬 호스트 외부 호출을 할 수 있다. 작은 테스트에 비해 다소 느리고 불안정한 경향이 있다.
큰 테스트: 로컬 호스트라는 제한을 없애 다른 컴퓨터를 호출한다. 구글에선 E2E 테스트에 사용한다.
다른 부분과 연동되지 않는 단위의 테스트
외부 요소 간의 통합을 테스트해야 할 때 사용하는 테스트 수준이다.
우리의 구성요소, 외부의 구성요소가 잘 통합되는지, 상호작용하는지 테스트 할 수 있다.
소프트웨어를 더 실질적인 관점에서 바라보고 현실적인 테스트를 수행한다. 시스템 내부가 어떻게 동작하는지 관심이 없다. 이런 입력을 주면 저런 출력이 나오는지에만 관심이 있다.
테스트가 현실적이다. 실제 웹 페이지를 방문하고 양식을 제출, 결과를 확인한다.
상황에 따르다. 잘못 선택한 테스트 수준은 비용을 많이 소모하고 버그를 충분히 찾을 수 없을 수도 있다.
살충제 역설을 명심하고 다양한 수준의 테스트를 수행해야 한다.
시스템 알고리즘이나, 단일 비즈니스 로직과 관련된 단위
테스트 대상 구성요소가 외부 구성요소와 상호작용할 때
테스트 대상 소프트웨어에서 절대적으로 중요한 부분에 대해, 버그가 발생하면 가장 크게 영향을 미치는 곳에 대해 작성한다.