소프트웨어 개발자/좋은 개발자 되기

[테스트 코드]에 대한 가이드라인 및 항목 정리 (feat. Python)

yubi5050 2024. 9. 22. 16:23

개요

테스트 코드를 작성시 어떤 도구로, 어떠한 것을 어떻게 작성 해야 될 까에 대한, 아직 경험이 부족하지만 이해한 바를 토대로 가이드라인 겸 항목 정리를 해보았다. (이해도가 높아질 때마다 계속 업데이트 해보는걸로..)

 

현재 개발 방식이 TDD(Test Driven Development)일 수도 있고 아닐 수도 있을 것이고, 스타트업 같은 만약 제품을 먼저 내야 하는 상황이면, 테스트 코드 작성 비용은 다소 부담스럽고 우선순위가 자연스럽게, 뒤로 밀릴 수 있다.

혹은 완성된 서비스의 안정성을 높이기 위해, 테스트 코드를 넣으려는 경우, 방대한 서비스 코드의 모든 부분에 대해 테스트 코드를 채워 넣는 것 또한 다소 비효율적으로 느껴질 수 있다.

 

따라서 테스트 코드 작성시 '모든 것에 테스트 코드를 작성할 필요는 없다' 라는 생각 하에 가성비가 좋은 대상 로직을 선택을 하는 것이 중요한 것 같다. (물론 관리만 잘 된다면 테스트 코드는 다다익선..)

 

테스트 코드 작성 대상 정하기

참고 : 단위 테스트의 가성비를 10초만에 판단하는 법 (Youtube 영상)

위 영상에서는 테스트 코드 우선순위를 정할 때 내가 가진 코드를 다음과 같은 사분면으로 표현하여 설명하는데, 굉장히 공감대는 부분도 많고 좋은 기준 인 것 같아 참고하였다.

 

가장 중요한 핵심은 '단위 테스트시 비용 대비 효과를 고려할 것' 이다.

 

우선 x축 : 협력자  (=프로젝트 규모), y축 : 복잡도 및 도메인 중요성을 나타내며, 보유한 코드가 어떠한 사분면에 속하는지 분류한다.

출처 : https://www.youtube.com/watch?v=qECd2q3USqA

 

  • 1사분면(간단한 코드) : 복잡도도 낮고, 협업자(연관 서비스)의 수도 적은 경우 에러가 발생할 확률도 높지 않기에 가장 우선순위가 낮음 (ex. 값 가져오기 등)
  • 2사분면(컨트롤러): 외부와 연동되어서 그 사이에서 데이터를 주고 받는 것 , 테스트를 하면 좋지만 가성비가 안 나올 수 있다.(외부 의존성에 대한 모킹 필요, but 모킹 관리의 어려움이 있음)
  • 3사분면(도메인 모델 및 알고리즘): 코어 로직(도메인 중요도가 높은 로직)이면서 비용은 상대적으로 낮아 테스트의 가치가 큰 경우 (ex. 캘린더 요일 계산, 데이터 파싱, 금액 계산 등)
  • 4사분면(지나치게 복잡한 코드): 해당 코드에 대한 테스트를 작성시 의존성도 크고 비용도 오히려 클 수 있다. → 리팩토링을 통해 2,3 사분면으로 코드를 재 분리 한다.

 

*테스트 작성시 효과는 코드의 복잡도, 도메인 관련성을 보고 판단, 테스트 비용은 외부 의존성 갯수로 가늠 할 수 있다고 함

 

즉 영상 내 결론에 따르면,

3사분면에 속하는 로직들 → 우선순위 높게 작성하고, 여유가 된다면 2사분면 까지,

4사분면에 속하는 것들은 2, 3사분면에 재 분리하는 리팩토링 과정을 우선적으로 거치는 것이 좋음

 

테스트 방식 정하기

테스트 코드 작성 대상을 정했다면 (혹은 대상을 정할 때 같이 고려해야 되는 것 같기도 하다) 테스트 할 방식을 정해야 하는데, 테스트에는 다음과 같은 종류들이 있다. 

  • 단위 테스트 : 개별 기능의 독립적 테스트 ( + 입출력 데이터의 유효성 검증, +데이터 무결성, 성능, 트랜잭션 관리 등)
  • E2E 통합 테스트 : 여러 모듈이 통합된 환경에서의 상호작용 확인, 사용자 플로우 기반 전체 시스템 검증
  • 성능 테스트 : 애플리케이션의 응답 시간 및 부하 테스트
  • 사용자 경험 테스트 : UI/UX의 직관성과 접근성 평가.
  • 호환성 테스트 : 다양한 브라우저, 장치, OS에서의 동작 확인
  • 보안 테스트 : 잠재적인 보안 취약점 탐지 (SQL Injection, XSS, CSRF 등)

등의 다양한 관점이 존재하며, 현실적으로 모든 테스트를 다 작성할 수도, 필요도 없기에 현실과 타협?하여 적절히 필요한 것들을 구현하면 된다. 

 

테스트 시나리오 작성

테스트 코드 작성 대상(사분면 내 로직)을 분류 하였고 방법을 선정했다면, 그에 맞는 테스트 시나리오를 작성 해야 하는데,

향후 테스트 코드를 유지 보수 하면서, 테스트 되어지고 있는 것/않는 것에 대한 진행 정도를 잘 파악하기 위해서라도, 시나리오 작성에 대한 체계적인 룰은 팀 내에 꼭 정해 놓는 이 좋은 것 같다.

 

개인적으로는 시나리오 작성시 테스트 코드를 실행/작성하는데 있어 비용을 조금이라도 더 줄이기 위해, 중복 테스트 되는 코드가 없도록 작성하는 것에 중점을 두는 편이다.

 

예로 유저가 로그인 후 글 조회나 생성과 같은 액션 (글 생성 간 로그인 권한을 필요로함) 을 할 수 있다고 할 때, 로그인 테스트 코드 에서 로그인 기능에 대한 테스트를 따로 작성하고, 조회나 생성시에는 로그인 되어 있음을 가정하여 그 이후 부터 테스트 할 수 있도록 한다. (로그인 된 유저를 모킹해서 사용)

 

ex) pytest 에서는 fixtures 개념을 통해 '글' 생성하는 로직 테스트시, '로그인이 완료 된 유저'를 fixtures로 정의해둘 수 있다. 만약 '글'에 '댓글'을 작성하는 로직을 테스트 한다면 fixture에 '작성 완료 된 글'을 사용한다.

 

추가로 만약 시나리오 작성에도 우선순위를 둔다면, 다양한 케이스 중에 중요도(사이드 이펙트가 클 수 있거나, 코어 로직이거나)가 높은 부분을 먼저 작성 하는 것도 좋고, 아니면 그냥 쉬운 것 부터 착착착 작성해 나가는 것도 좋은 것 같다. (손에 익게 연습도 할겸) 그냥 개인적으론 테스트 코드 폴더 옆에 마크다운으로 슥슥슥 적어 나갔었다.

 

* 정량적인 관점에서 단위 테스트의 이상적인 테스트 코드 케이스 최소 갯수 → 해당 API의 세부 에러 코드 갯수 이지 않을까 막연하게 생각해본다.

 

테스트 코드 작성 

시나리오에 맞춰 테스트 코드를 작성하는 과정이다. 

 

일반적으로는 Given-When-Then 패턴으로 테스트 코드 구성을 많이 하는 것 같다.

Given-When-Then 패턴 이란 Test Code에 대한 스타일을 [준비 - 실행 - 검증] 으로 표현 하는 방식

(ref. https://martinfowler.com/bliki/GivenWhenThen.html)

 

Given : 테스트 시작 전 테스트 상태, 사전 조건 (기본 데이터 값 등)를 설명하는 부분

When : 액션을 하는 테스트를 실행하는 과정

Then : 테스트 결과를 검증 하는 과정

 

테스트 코드 작성 세부 케이스

시나리오 대로 작성하였지만.. 대부분 기획적으로 정해진 내용들이 대부분 이긴 하다. 이정도만을 갖추는데에도? 수고스러웠겠지만, 가끔 어떤 것을 더 테스트하면 좋을까? 고민이 종종 들 때가 있었던 것 같다. 사실 이부분은 근데 로직도 그렇고 정해진 것은 없는 것 같고, 자기 만족을 위해서나, 혹여나 시간이 지남에 따라 모르는 거니까 하는 마음에 더 작성하는 것도 가능한 것 같다.

아래는 테스트 방식에 따라 기본적으로 작성되면 괜찮을 것 같다거나, 혹은 작성했던 세부 케이스들에 대해 막연하게 적어보았다. (아이디어가 떠오르면 또 추가를..)

 

단위 테스트 (UnitTest)

  1. 응답 상태 코드 유효한지 (200, 400, 401, 403, 404, 301(리디렉션) 등)
  2. 파라미터 테스트 : Query 파라미터 / Path 파라미터가 유효하지 않을 때 의도한 결과가 나오는지
  3. 요청/응답 헤더 테스트 : 요청 헤더가 없을 때 의도한 결과가 나오는지 / 응답 헤더에 올바른 데이터가 반환되는지
  4. 인증, 권한 등의 이슈에 401 Unauthorized, 403 Forbidden이 적절히 반환되는지 (의도한 결과가 나오는지)
  5. Body : 잘못된 데이터나 누락된 필드를 전송시 400 (Bad Reqeust)등 적절한 오류 메시지 반환되는지
  6. 데이터 유효성 검사 : 잘못된 데이터 형식, 길이 등에 대한 유효성 검사가 잘 되는지
  7. 중복 요청 : 동일한 요청 발생시 멱등성 처리가 적절한 잘 되는 지
  8. 다양한 상황에 대해 결과 Response가 의도한 대로 잘 나오는지
  9. (DB) 데이터 생성/수정/삭제 등의 과정을 거친 후 실제 데이터에 반영되는지

 

성능 테스트 (Performance Test)

  • 응답 시간: 서버가 요청에 응답하는 데 걸리는 시간 측정 (ex. 많은 데이터 내보내는 서비스 화면에 응답 속도 3초 이하 확인)
  • 부하 처리: 동시에 여러 사용자가 접속시 데이터가 잘 처리 되는지 확인 (특정 시나리오 기반으로)
  • 스트레스 테스트: 서버에 과도한 부하를 걸어 한계점을 파악 (특정 시나리오 기반으로)

etc..

 

테스트 코드 도구, 라이브러리

Python에서 테스트 코드 관련 쓰이거나 활용할 수 있는 도구, 라이브러리 등을 정리해보았다.

 

unittest : Python에 기본 내장된 테스트 프레임워크

  • SetUp 및 TearDown 메서드를 통해 테스트 환경을 준비하고 정리
  • 테스트 케이스 작성, 테스트 실행, 결과 보고 기능 포함. 그룹화된 테스트 실행 가능

 

pytest : 간결한 문법과 확장성을 제공

  • 가장 인기 있는 테스트 프레임워크, 간단한 함수 기반 테스트 작성
  • 직관적이고 간결한 문법. / 속도와 편리성
  • pytest.mark.adv_one_flow를 이용하여 시나리오를 정의하고 시나리오 대로 실행도 가능
  • django-pytest : django와 통합되어 사용 가능
  • fastapi는 httpx 나 fastapi-asyncio 를 통해 비동기 테스팅 가능

 

mock : 객체 모킹 라이브러리

  • unittest.mock으로 내장되어 있으며, 외부 의존성(함수 호출, 속성 액세스 등) 모킹하여 테스트
  • 네트워크 호출, 데이터베이스 연결 등의 외부 의존성 없이 테스트 가능.
  • freezegun : 시간 mocking 라이브러리 (코드가 실행되는 특정 시점 시간 고정 및 시간 임의 조작 테스팅 가능)
  • faker : 가짜 데이터를 생성하여 테스트 데이터를 모킹
  • moto, botocore.stubber : aws 서비스를 모킹해주는 라이브러리 (moto는 aws 서비스 자체를, stubber는 api를 모킹)

 

coverage.py : 테스트 커버리지 측정 도구

  • 테스트 코드가 커버하는 영역의 코드 커버리지 보고서를 생성 및 테스트 효과를 높임 
  • CI 시스템과 통합하여 테스트 품질 관리에 유용.