이펙티브 소프트웨어 테스팅 - 마우리시오 아니시.

최근 프론트엔드 테스트에 관심이 생기게 되면서 주변 분들에게 추천받은 책입니다. 개인적인 하반기 목표로 팀 프로젝트에 유닛, 통합 테스트를 도입하려 하는 데 도움이 될 것 같아 읽어봤습니다.

테스트 케이스 도출, 상황에 따른 테스트 기법, 코드 커버리지와 같은 효율적이고 체계적인 테스트 작성법에 대해 서술합니다. 저의 경우 매 스프린트마다 피쳐들을 개발하는데 치여 테스트 코드를 작성할 엄두가 안 났었는데, 테스트 코드에 대해 얼마큼 시간을 할애할 수 있을지에 대한 지표가 될 것 같습니다.

다양한 테스트 기법들을 예제와 함께 소개합니다. 하나의 기법만으로 테스트하는 것을 지양합니다. 평소 e2e 테스트 위주로 작성해 단위, 통합 테스트에 대해 자신이 있지는 않았는데 작은 범위 단계에서 코드 커버리지의 이해와 평소에 생각하지 못했던 다양한 케이스들에 대한 예시들을 익힐 수 있었습니다.

TDD에 관한 내용은 짧게 있습니다. 무조건 적인 TDD보다 상황에 따라, 테스트 코드를 작성하는 개발자의 역량에 맞춰 작성하는 법을 제시합니다. 코드 작성의 연습을 강조합니다. 연습을 통해 코드를 작성하면서 본인에게 가장 효율적인 방법을 찾는 것을 강조합니다.

저자가 유닛 테스트를 선호해서 인지 유닛 테스트에 대한 내용이 많습니다.

책의 전반적인 내용들을 요약하는 1장을 정리해 봤습니다.

책을 통해 달성하고자 하는 것

  1. 개발자로서 만들고 있는 제품의 품질은 우리의 책임이다.
  2. 테스트는 그런 책임감을 갖는 데 도움을 줄 수 있는 유일한 도구다.
  3. 이 책을 읽고 테스트를 효율적이고 체계적으로 작성한다.

개발자를 위한 효율적인 소프트웨어

1. 효율적인 테스트란

  • 올바른 테스트를 작성하는데 집중해야 한다.
  • 무엇을 테스트해야 하는 지 알면 된다.
  • 시작점과 끝점을 잘 파악해 필요한 테스트 커버리지를 작성해야 한다.

2. 체계적인 테스트란

  • 어떤 코드에 대해 어느 개발자라도 같은 테이스 케이스를 만드는 것을 의미한다.

3. 다양한 테스트 기법

  1. 도메인 테스트: 요구사항, 명세에 대한 테스트
  2. 구조적 테스트: 소프트웨어에 대한 일반적인 테스트
  3. 예시 기반 테스트: 다양한 case 중 임의의 case 테스트
  4. 속성 기반 테스트: 다양한 case 중 특정한 case 테스트
  5. 계약, 사전, 사후 조건 (제품 소유자가 놓친 case or 논리적 오류를 말하는 듯합니다.)

개발자를 위한 효율적이고 체계적인 소프트웨어 테스트는 위의 5가지 경우의 테스트를 적절히 사용해야 한다.

4. 개발 과정에서의 효율적인 테스트 예시

  1. 기능 개발의 요구사항을 받아 분석하고 코드를 작성한다.

  2. 짧은 TDD 과정을 반복한다. 이 과정에서 피드백을 받고, 리팩터링을 진행한다.

  3. 요구사항이 커지게 되면 쪼개 서로 어울려 전체 기능을 구성한다.

  4. 요구사항을 충족한다고 생각하면 테스트를 작성한다. 새로 만든 각 단위를 테스트한다. (도메인, 경계, 구조적 테스트)

  5. 대규모 테스트가 필요하다면 진행한다. (통합, 시스템 테스트) 이때는 큰 시스템의 큰 부분만 살펴본다.

  6. 다양한 기법으로 테스트 도구를 만들었다면 지능형 테스트 도구를 써 사람이 못 찾는 케이스를 찾는다. (테스트 케이스 생성, 돌연변이 테스트, 정적 분석..)

예시일 뿐 따를 필요는 없고 자신에게 가장 적합하고 생산적인 방법을 찾아야 한다. 과정이 선형적으로 보이지만, 필요하다면 언제든지 처음으로 돌아가야 한다.

5. 개발에 먼저 집중하고 나서 테스트하기

개발과 테스트를 분리하자. 개발하면서 생각나는 테스트 케이스는 어디엔가 기록한다. 개발을 마치고 나서 테스트를 작성할 땐 일반적인 테스트를 작성하고, 추가로 창의성과 도메인 지식을 이용해 테스트를 작성한다.

6. 테스트 비용

테스트 코드를 작성하는 데는 비용이 많이 든다. 하지만 버그를 제때 찾지 못해 발생한 장애의 손실이 더 크다. 테스트 케이스 작성의 비용은 개발자의 역량이 중요하다. 작성 비용이 덜 들도록 연습을 통해 익숙해져야 한다.

7. 테스트 자동화의 역할

테스트 케이스를 설계하는 것과 실행하는 것은 별개다. 테스트 프레임워크는 실행하고, 보고서와 실패 사유 등을 만들어 주는 것이 전부다.

소프트웨어 테스트 원칙

1. 완벽한 테스트는 불가능하다.

모든 걸 테스트하는 것은 불가능하다. 따라서 효율적인 테스트를 해야 한다.

2. 테스트를 그만둘 때를 파악하기

우리의 목표는 비용을 최소화하고 최대한 많은 버그를 찾는 것이다. 이것을 위해 테스트를 언제 그만둘 건지에 대해 고민해야 한다.

3. 가변성이 중요하다.(살충제 역설)

다양한 테스트 기법을 사용하자. 하나의 테스트 기법으로만 테스트를 한다면 그 테스트 수준 외의 버그는 찾을 수가 없다.

4. 버그는 다른 곳에 비해 많이 발생하는 지점이 있다. (결함 클러스터링)

가령 머케팅 모듈보다 결제 모듈에 많은 버그가 발생한다. 이런 곳에 우선순위를 더 두어 엄격한 테스트를 진행할 수 있도록 한다.

5. 어떤 테스트를 하든지 결코 완벽하거나 충분하지는 않다.

테스트가 얼마나 많든 간에 소프트웨어의 버그가 100% 존재하지 않는다는 것을 보장하지 않는다. 테스트는 시스템이 우리가 기대하는 대로 동작하는지를 검증하는 케이스만을 보장한다.

6. 맥락이 핵심이다.

어떤 소프트웨어를 테스트하냐에 따라 테스트 기법이 달라질 수 있다.

7. 검증은 유효성 검사가 아니다.

오류 없이 동작하지만 사용자에게 쓸모없는 소프트웨어 시스템은 좋은 시스템이 아니다.

검증은 시스템이 제대로 되어 있는가에 관한 것 (오류 없이 동작하는가), 유효성 검사 (쓸모 있는가) 는 올바른 시스템을 가지는 방법에 관한 것이다.

검증과 유효성 검사는 함께 진행해야 한다. 유효성 검사를 진행하면서 기획자가 생각하지 못한 코너 케이스를 발현할 수도 있다.

테스트에서 집중해야 할 부분

테스트 수준에 따라 단위 테스트, 통합 테스트, 시스템 테스트가 있다.

테스트 크기에 따른 정의

작은 테스트: 단일 프로세스에서 실행할 수 있는 테스트. 빠르며 불안정하지 않다.

중간 크기 테스트: 여러 프로세스에 걸쳐 실행될 수 있고, 스레드를 사용할 수 있으며 로컬 호스트 외부 호출을 할 수 있다. 작은 테스트에 비해 다소 느리고 불안정한 경향이 있다.

큰 테스트: 로컬 호스트라는 제한을 없애 다른 컴퓨터를 호출한다. 구글에선 E2E 테스트에 사용한다.

1. 단위 테스트

다른 부분과 연동되지 않는 단위의 테스트

장점

  • 빠르다. 지속적인 피드백으로 편안함과 자심감을 심어준다.
  • 다루기 쉽다. 입렵 값과 결과 값만을 신경쓰면 된다.
  • 작성하기 쉽다. 응집력이 있고 크기가 작아 쉽게 작성할 수 있다.

단점

  • 현실성이 떨어진다. 전체 서비스의 테스트와는 거리가 멀다. 단지 작은 부분을 테스트할 뿐이다.
  • 잡을 수 없는 종류의 버그가 존재한다. 복합적인 모듈의 버그를 잡을 순 없다.

2. 통합 테스트

외부 요소 간의 통합을 테스트해야 할 때 사용하는 테스트 수준이다.

장점 우리의 구성요소, 외부의 구성요소가 잘 통합되는지, 상호작용하는지 테스트 할 수 있다.

단점

  • 단위 테스트에 비해 어렵다.
  • 실제 DB에 연동을 한다면 별도의 테스트에 사용할 DB를 구축해야 할 때가 있다.

3. 시스템 테스트

소프트웨어를 더 실질적인 관점에서 바라보고 현실적인 테스트를 수행한다. 시스템 내부가 어떻게 동작하는지 관심이 없다. 이런 입력을 주면 저런 출력이 나오는지에만 관심이 있다.

장점 테스트가 현실적이다. 실제 웹 페이지를 방문하고 양식을 제출, 결과를 확인한다.

단점

  • 느리다. 실제 애플리케이션과 상호작용해야 하고 몇 초가 걸릴 수도 있다. (HTTP 요청을 보내고 JSON 응답을 기록하는 행위..)
  • 작성하기 힘들다. 복잡한 설정이 필요하다. DB에 연결하고, 인증하고, 테스트를 위한 데이터가 있는지 확인해야 한다.
  • 불안정한 경향이 있다. 환경에 따라 불규칙하게 동작하는 불확실성이 있다.

4. 언제 사용해야 할까

상황에 따르다. 잘못 선택한 테스트 수준은 비용을 많이 소모하고 버그를 충분히 찾을 수 없을 수도 있다.

5. 각 수준에서 무엇을 테스트해야 할까?

살충제 역설을 명심하고 다양한 수준의 테스트를 수행해야 한다.

단위 테스트
시스템 알고리즘이나, 단일 비즈니스 로직과 관련된 단위

통합 테스트
테스트 대상 구성요소가 외부 구성요소와 상호작용할 때**

시스템 테스트 테스트
대상 소프트웨어에서 절대적으로 중요한 부분에 대해, 버그가 발생하면 가장 크게 영향을 미치는 곳에 대해 작성한다.