긍정적인 테스트 경험을 위한 8가지 자동 테스트 모범 사례
게시 됨: 2022-03-11많은 개발자들이 테스트를 시간과 에너지를 소모하는 필요악으로 보는 것은 당연합니다. 테스트는 지루하고 비생산적이며 완전히 복잡할 수 있습니다.
테스트에 대한 나의 첫 경험은 끔찍했습니다. 나는 엄격한 코드 커버리지 요구 사항이 있는 팀에서 일했습니다. 워크플로는 기능을 구현하고, 디버그하고, 전체 코드 적용 범위를 보장하기 위한 테스트를 작성하는 것이었습니다. 팀에는 통합 테스트가 없었고, 수동으로 초기화된 수많은 모의가 있는 단위 테스트만 있었고, 대부분의 단위 테스트는 라이브러리를 사용하여 자동 매핑을 수행하는 동안 사소한 수동 매핑을 테스트했습니다. 모든 테스트는 사용 가능한 모든 속성을 주장하려고 하므로 모든 변경이 수십 개의 테스트를 중단했습니다.
테스트는 시간이 많이 걸리는 부담으로 여겨져 작업하는 것을 싫어했습니다. 그러나 나는 포기하지 않았다. 신뢰 테스트가 제공되고 모든 작은 변경 후 검사의 자동화가 내 관심을 불러일으켰습니다. 나는 읽기와 연습을 시작했고, 테스트가 제대로 이루어지면 도움이 되고 즐거울 수 있다는 것을 배웠습니다.
이 기사에서는 처음부터 알았더라면 좋았을 8가지 자동화 테스트 모범 사례를 공유합니다.
자동화된 테스트 전략이 필요한 이유
자동화된 테스트는 종종 미래에 초점을 맞추지만 올바르게 구현하면 즉시 이점을 얻을 수 있습니다. 작업을 더 잘 수행하는 데 도움이 되는 도구를 사용하면 시간을 절약하고 작업을 더 즐겁게 할 수 있습니다.
회사의 ERP에서 구매 주문을 검색하고 해당 주문을 공급업체에 보내는 시스템을 개발 중이라고 상상해 보십시오. ERP에 이전에 주문한 항목의 가격이 있지만 현재 가격이 다를 수 있습니다. 더 낮은 가격 또는 더 높은 가격으로 주문할지 여부를 제어하려고 합니다. 사용자 기본 설정이 저장되어 있고 가격 변동을 처리하는 코드를 작성 중입니다.
코드가 예상대로 작동하는지 어떻게 확인하시겠습니까? 당신은 아마:
- ERP의 개발자 인스턴스에서 더미 주문을 만듭니다(미리 설정했다고 가정).
- 앱을 실행합니다.
- 해당 주문을 선택하고 주문 프로세스를 시작합니다.
- ERP 데이터베이스에서 데이터를 수집합니다.
- 공급업체의 API에서 현재 가격을 요청합니다.
- 코드에서 가격을 재정의하여 특정 조건을 만듭니다.
중단점에서 멈췄고 단계별로 이동하여 한 시나리오에 대해 어떤 일이 일어날지 확인할 수 있지만 가능한 시나리오는 많이 있습니다.
| 기본 설정 | ERP 가격 | 공급업체 가격 | 주문을 해야 할까요? | |
|---|---|---|---|---|
| 더 높은 가격 허용 | 더 낮은 가격 허용 | |||
| 거짓 | 거짓 | 10 | 10 | 진실 |
| (여기에는 3개의 선호 조합이 더 있지만 가격이 동일하므로 결과는 동일합니다.) | ||||
| 진실 | 거짓 | 10 | 11 | 진실 |
| 진실 | 거짓 | 10 | 9 | 거짓 |
| 거짓 | 진실 | 10 | 11 | 거짓 |
| 거짓 | 진실 | 10 | 9 | 진실 |
| 진실 | 진실 | 10 | 11 | 진실 |
| 진실 | 진실 | 10 | 9 | 진실 |
버그가 있는 경우 회사는 돈을 잃거나 명성을 훼손하거나 둘 다일 수 있습니다. 여러 시나리오를 확인하고 확인 루프를 여러 번 반복해야 합니다. 수동으로 수행하는 것은 지루할 것입니다. 그러나 여기에 도움이 되는 테스트가 있습니다!
테스트를 사용하면 불안정한 API에 대한 호출 없이 컨텍스트를 만들 수 있습니다. 레거시 ERP 시스템에서 흔히 볼 수 있는 오래되고 느린 인터페이스를 반복적으로 클릭할 필요가 없습니다. 장치 또는 하위 시스템에 대한 컨텍스트를 정의하기만 하면 모든 디버깅, 문제 해결 또는 시나리오 탐색이 즉시 수행됩니다. 테스트를 실행하면 코드로 돌아갑니다. 내가 선호하는 것은 내 IDE에서 이전 테스트 실행을 반복하는 키 바인딩을 설정하여 변경하는 즉시 자동화된 피드백을 제공하는 것입니다.
1. 올바른 태도 유지
수동 디버깅 및 자체 테스트와 비교할 때 자동화된 테스트는 테스트 코드가 커밋되기 전에도 처음부터 더 생산적입니다. 수동으로 테스트하거나 더 복잡한 모듈의 경우 테스트 중에 디버거를 사용하여 단계별로 코드가 예상대로 작동하는지 확인한 후 어설션을 사용하여 입력 매개변수의 조합에 대해 예상하는 것을 정의할 수 있습니다.
테스트를 통과하면 거의 커밋할 준비가 되었지만 아직 완료되지 않았습니다. 첫 번째 작업 버전은 일반적으로 우아하지 않으므로 코드 리팩터링을 준비합니다. 테스트 없이 리팩토링을 수행하시겠습니까? 모든 수동 단계를 다시 완료해야 하므로 열정이 줄어들 수 있기 때문에 의심스럽습니다.
미래는 어떻습니까? 리팩토링, 최적화 또는 기능 추가를 수행하는 동안 테스트는 모듈을 변경한 후에도 모듈이 예상대로 작동하는지 확인하는 데 도움이 됩니다.
테스트를 부담으로 생각하거나 코드 검토자나 리드만 행복하게 만드는 것으로 생각하는 것은 비생산적입니다. 테스트는 개발자로서 우리가 혜택을 볼 수 있는 도구입니다. 우리는 코드가 작동하는 것을 좋아하고 반복적인 작업이나 버그를 해결하기 위해 코드를 수정하는 데 시간을 소비하는 것을 좋아하지 않습니다.
최근에 저는 코드베이스에서 리팩토링 작업을 했고 사용하지 않는 using 지시문을 정리하도록 IDE에 요청했습니다. 놀랍게도 테스트 결과 내 이메일 보고 시스템에서 몇 가지 오류가 나타났습니다. 그러나 그것은 유효한 실패였습니다. 정리 프로세스가 이메일 템플릿에 대한 Razor(HTML + C#) 코드의 일부 using 지시문을 제거했으며 템플릿 엔진이 결과적으로 유효한 HTML을 빌드할 수 없었습니다. 그런 사소한 작업으로 이메일 보고가 중단될 거라고는 예상하지 못했습니다. 테스트를 통해 모든 것이 제대로 작동할 것이라고 가정했을 때 출시 직전에 앱 전체에서 버그를 잡는 데 시간을 허비하는 것을 피할 수 있었습니다.
물론 도구 사용법을 알아야 하고 속담에 손을 베지 않아야 합니다. 컨텍스트를 정의하는 것은 지루하고 앱을 실행하는 것보다 어려울 수 있습니다. 테스트는 오래되고 쓸모 없게 되는 것을 피하기 위해 너무 많은 유지 관리가 필요합니다. 이것들은 유효한 포인트이며 우리는 그것들을 다룰 것입니다.
2. 올바른 유형의 테스트 선택
개발자들은 종종 코드에 의해 호출되는지 확인하기 위해 수십 개의 종속성을 조롱하려고 하기 때문에 자동화된 테스트를 싫어하게 됩니다. 또는 개발자는 높은 수준의 테스트에 직면하고 모든 애플리케이션 상태를 재현하여 작은 모듈의 모든 변형을 확인하려고 합니다. 이러한 패턴은 비생산적이고 지루하지만 의도한 대로 다양한 테스트 유형을 활용하여 이러한 패턴을 피할 수 있습니다. (테스트는 결국 실용적이고 즐거워야 합니다!)
독자는 단위 테스트가 무엇이고 어떻게 작성하는지 알아야 하고 통합 테스트에 익숙해야 합니다. 그렇지 않은 경우 여기에서 속도를 내기 위해 잠시 멈출 가치가 있습니다.
수십 가지 테스트 유형이 있지만 다음 5가지 일반적인 유형은 매우 효과적인 조합을 만듭니다.
- 단위 테스트 는 해당 메서드를 직접 호출하여 격리된 모듈을 테스트하는 데 사용됩니다. 종속성은 테스트되지 않으므로 조롱됩니다.
- 통합 테스트 는 하위 시스템을 테스트하는 데 사용됩니다. 여전히 모듈의 자체 메서드에 대한 직접 호출을 사용하지만 여기서는 종속성에 대해 관심을 가지므로 모의 종속성을 사용하지 말고 실제(프로덕션) 종속 모듈만 사용합니다. 인메모리 데이터베이스나 모의 웹 서버는 인프라의 모형이기 때문에 계속 사용할 수 있습니다.
- 기능 테스트 는 종단 간(E2E) 테스트라고도 하는 전체 애플리케이션에 대한 테스트입니다. 직접 통화를 사용하지 않습니다. 대신 모든 상호 작용은 API 또는 사용자 인터페이스를 통해 진행됩니다. 이는 최종 사용자 관점의 테스트입니다. 그러나 인프라는 여전히 조롱되고 있습니다.
- 카나리아 테스트 는 기능 테스트와 유사하지만 프로덕션 인프라와 더 작은 작업 세트가 있습니다. 새로 배포된 응용 프로그램이 작동하는지 확인하는 데 사용됩니다.
- 부하 테스트 는 카나리아 테스트와 유사하지만 실제 스테이징 인프라와 여러 번 반복되는 훨씬 더 작은 작업 집합을 사용합니다.
처음부터 5가지 테스트 유형을 모두 사용할 필요는 없습니다. 대부분의 경우 처음 세 가지 테스트로 먼 길을 갈 수 있습니다.
각 유형의 사용 사례를 간략하게 검토하여 필요에 맞는 것을 선택하는 데 도움을 드리겠습니다.
단위 테스트
가격과 취급 기본 설정이 다른 예를 생각해 보십시오. 모듈 내부에서 일어나는 일에만 관심이 있고 그 결과가 중요한 비즈니스 영향을 미치기 때문에 단위 테스트에 적합한 후보입니다.
모듈에는 입력 매개변수의 다양한 조합이 있으며 유효한 인수의 모든 조합에 대해 유효한 반환 값을 얻으려고 합니다. 단위 테스트는 함수 또는 메서드의 입력 매개변수에 직접 액세스할 수 있고 모든 조합을 포함하기 위해 수십 개의 테스트 메서드를 작성할 필요가 없기 때문에 유효성을 보장하는 데 좋습니다. 많은 언어에서 코드 및 예상 결과에 필요한 인수를 허용하는 메서드를 정의하여 테스트 메서드의 중복을 방지할 수 있습니다. 그런 다음 테스트 도구를 사용하여 매개변수화된 방법에 대해 다양한 값과 기대치를 제공할 수 있습니다.
통합 테스트
통합 테스트는 모듈이 종속성, 다른 모듈 또는 인프라와 상호 작용하는 방식에 관심이 있는 경우에 적합합니다. 여전히 직접 메서드 호출을 사용하지만 하위 모듈에 액세스할 수 없으므로 모든 하위 모듈의 모든 입력 메서드에 대해 모든 시나리오를 테스트하는 것은 비현실적입니다.
일반적으로 모듈당 하나의 성공 시나리오와 하나의 실패 시나리오를 선호합니다.
저는 통합 테스트를 사용하여 종속성 주입 컨테이너가 성공적으로 빌드되었는지, 처리 또는 계산 파이프라인이 예상 결과를 반환하는지 또는 복잡한 데이터를 데이터베이스 또는 타사 API에서 올바르게 읽고 변환했는지 여부를 확인하는 것을 좋아합니다.
기능 또는 E2E 테스트
이러한 테스트는 앱이 최소한 런타임 오류 없이 시작할 수 있는지 확인하기 때문에 앱이 작동한다는 가장 확신을 줍니다. 클래스에 직접 액세스하지 않고 코드 테스트를 시작하는 것은 약간 더 많은 작업이 필요하지만 처음 몇 가지 테스트를 이해하고 작성하면 그리 어렵지 않다는 것을 알게 될 것입니다.
필요한 경우 명령줄 인수로 프로세스를 시작하여 애플리케이션을 실행한 다음 잠재 고객이 API 엔드포인트를 호출하거나 버튼을 누르는 것처럼 애플리케이션을 사용합니다. 이것은 UI 테스트의 경우에도 어렵지 않습니다. 각 주요 플랫폼에는 UI에서 시각적 요소를 찾는 도구가 있습니다.
카나리아 테스트
기능 테스트를 통해 앱이 테스트 환경에서 작동하는지 알 수 있지만 프로덕션 환경은 어떻습니까? 여러 타사 API로 작업 중이고 해당 상태의 대시보드를 원하거나 애플리케이션이 수신 요청을 처리하는 방법을 확인하려고 한다고 가정합니다. 다음은 카나리아 테스트의 일반적인 사용 사례입니다.
그들은 제 3 자 시스템에 부작용을 일으키지 않고 작업 시스템에 잠시 작용하여 작동합니다. 예를 들어, 주문을 하지 않고도 신규 사용자를 등록하거나 제품 재고를 확인할 수 있습니다.
카나리아 테스트의 목적은 모든 주요 구성 요소가 예를 들어 자격 증명 문제로 인해 실패하지 않고 프로덕션 환경에서 함께 작동하는지 확인하는 것입니다.
부하 테스트
부하 테스트는 많은 사람들이 사용하기 시작할 때 응용 프로그램이 계속 작동할지 여부를 나타냅니다. 카나리아 및 기능 테스트와 유사하지만 로컬 또는 프로덕션 환경에서 수행되지 않습니다. 일반적으로 프로덕션 환경과 유사한 특수 스테이징 환경이 사용됩니다.
이러한 테스트는 실제 제3자 서비스를 사용하지 않는다는 점에 유의하는 것이 중요합니다. 이러한 서비스는 프로덕션 서비스의 외부 부하 테스트에 만족하지 않을 수 있으며 결과적으로 추가 요금이 부과될 수 있습니다.
3. 테스트 유형을 별도로 유지
자동화된 테스트 계획을 세울 때 각 테스트 유형을 분리하여 독립적으로 실행할 수 있어야 합니다. 여기에는 추가 구성이 필요하지만 테스트를 혼합하면 문제가 발생할 수 있으므로 가치가 있습니다.
이러한 테스트에는 다음과 같은 차이점이 있습니다.
- 의도와 기본 개념(따라서 이들을 분리하는 것은 "미래의 당신"을 포함하여 코드를 보는 다음 사람에게 좋은 선례가 됩니다).
- 실행 시간(따라서 먼저 단위 테스트를 실행하면 테스트가 실패할 때 더 빠른 테스트 주기를 허용합니다).
- 종속성(따라서 테스트 유형 내에서 필요한 것만 로드하는 것이 더 효율적입니다).
- 필요한 인프라.
- 프로그래밍 언어(특정 경우).
- CI(지속적 통합) 파이프라인 또는 외부에 있는 위치.
대부분의 언어 및 기술 스택에서 예를 들어 기능 모듈의 이름을 딴 하위 폴더와 함께 모든 단위 테스트를 그룹화할 수 있다는 점에 유의하는 것이 중요합니다. 이것은 편리하고 새로운 기능 모듈을 생성할 때 마찰을 줄이며 자동화된 빌드에 더 쉽고 결과적으로 덜 복잡하며 테스트를 단순화하는 또 하나의 방법입니다.
4. 자동으로 테스트 실행
몇 가지 테스트를 작성했지만 몇 주 후에 리포지토리를 가져온 후 해당 테스트가 더 이상 통과하지 않는다는 것을 알게 되는 상황을 상상해 보십시오.
이것은 테스트가 코드이며 다른 코드와 마찬가지로 유지 관리해야 한다는 불쾌한 알림입니다. 이를 위한 가장 좋은 시간은 작업을 완료했다고 생각하고 모든 것이 여전히 의도한 대로 작동하는지 확인하려는 순간 직전입니다. 필요한 모든 컨텍스트가 있으며 다른 하위 시스템에서 작업하는 동료보다 더 쉽게 코드를 수정하거나 실패한 테스트를 변경할 수 있습니다. 그러나 이 순간은 마음속에만 존재하므로 테스트를 실행하는 가장 일반적인 방법은 자동으로 개발 분기로 푸시한 후 또는 풀 요청을 생성한 후입니다.

이렇게 하면 메인 브랜치가 항상 유효한 상태가 되거나 최소한 그 상태를 명확하게 알 수 있습니다. 자동화된 구축 및 테스트 파이프라인 또는 CI 파이프라인은 다음을 지원합니다.
- 코드를 빌드할 수 있는지 확인합니다.
- 잠재적인 "내 컴퓨터에서 작동" 문제를 제거합니다.
- 개발 환경을 준비하는 방법에 대한 실행 가능한 지침을 제공합니다.
이 파이프라인을 구성하는 데는 시간이 걸리지만 파이프라인은 단일 개발자인 경우에도 사용자나 클라이언트에 도달하기 전에 다양한 문제를 드러낼 수 있습니다.
CI는 일단 실행되면 범위가 확장되기 전에 새로운 문제를 드러냅니다. 그래서 저는 1차 테스트를 작성한 직후에 설정하는 것을 선호합니다. GitHub의 개인 리포지토리에서 코드를 호스팅하고 GitHub 작업을 설정할 수 있습니다. 리포지토리가 공개된 경우 GitHub 작업보다 더 많은 옵션이 있습니다. 예를 들어, 내 자동화된 테스트 계획은 데이터베이스와 세 가지 유형의 테스트가 있는 프로젝트에 대해 AppVeyor에서 실행됩니다.
저는 프로덕션 프로젝트를 위한 파이프라인을 다음과 같이 구성하는 것을 선호합니다.
- 편집 또는 번역
- 단위 테스트: 빠르고 종속성이 필요하지 않습니다.
- 데이터베이스 또는 기타 서비스의 설정 및 초기화
- 통합 테스트: 코드 외부에 종속성이 있지만 기능 테스트보다 빠릅니다.
- 기능 테스트: 다른 단계가 성공적으로 완료되면 전체 앱 실행
카나리아 테스트나 부하 테스트가 없습니다. 세부 사항과 요구 사항 때문에 수동으로 시작해야 합니다.
5. 필요한 테스트만 작성
모든 코드에 대한 단위 테스트를 작성하는 것은 일반적인 전략이지만 때로는 시간과 에너지를 낭비하고 자신감을 주지 못합니다. "테스트 피라미드" 개념에 익숙하다면 모든 코드가 단위 테스트로 다루어져야 하고 다른 상위 레벨 테스트에서는 하위 집합만 포함되어야 한다고 생각할 수 있습니다.
여러 모의 종속성이 원하는 순서로 호출되도록 하는 단위 테스트를 작성할 필요가 없습니다. 그렇게 하려면 여러 모의를 설정하고 모든 호출을 확인해야 하지만 여전히 모듈이 작동하고 있다는 확신을 주지는 못합니다. 일반적으로 실제 종속성을 사용하고 결과만 확인하는 통합 테스트만 작성합니다. 이는 테스트된 모듈의 파이프라인이 제대로 작동하고 있다는 확신을 줍니다.
일반적으로 기능을 구현하고 나중에 지원하면서 내 삶을 더 쉽게 만드는 테스트를 작성합니다.
대부분의 응용 프로그램에서 100% 코드 적용을 목표로 하면 지루한 작업이 많이 추가되고 일반적으로 테스트 및 프로그래밍 작업에서 즐거움을 제거합니다. Martin Fowler의 Test Coverage는 다음과 같이 설명합니다.
테스트 커버리지는 코드베이스의 테스트되지 않은 부분을 찾는 데 유용한 도구입니다. 테스트 커버리지는 테스트가 얼마나 좋은지에 대한 수치 설명으로 거의 사용되지 않습니다.
따라서 몇 가지 테스트를 작성한 후 커버리지 분석기를 설치하고 실행하는 것이 좋습니다. 강조 표시된 코드 행이 있는 보고서는 실행 경로를 더 잘 이해하고 다루어야 하는 발견되지 않은 위치를 찾는 데 도움이 됩니다. 또한 getter, setter 및 파사드를 살펴보면 100% 적용 범위가 재미 없는 이유를 알 수 있습니다.
6. 레고 놀이
때때로 “비공개 메서드를 테스트하려면 어떻게 해야 하나요?”와 같은 질문을 봅니다. 당신은하지 않습니다. 그런 질문을 했다면 이미 뭔가 잘못되었습니다. 일반적으로 단일 책임 원칙을 위반했으며 모듈이 제대로 수행하지 않는다는 의미입니다.
이 모듈을 리팩토링하고 중요하다고 생각하는 로직을 별도의 모듈로 가져옵니다. 파일 수를 늘리는 데 문제가 없습니다. 코드는 매우 읽기 쉽고, 유지 관리 가능하고, 교체 가능하고, 테스트 가능합니다.
코드를 적절하게 구성하는 것은 말보다 쉽습니다. 다음은 두 가지 제안 사항입니다.
함수형 프로그래밍
함수형 프로그래밍의 원리와 아이디어에 대해 배울 가치가 있습니다. C, C++, C#, Java, Assembly, JavaScript, Python과 같은 대부분의 주류 언어는 기계용 프로그램을 작성하도록 강요합니다. 함수형 프로그래밍은 인간의 두뇌에 더 적합합니다.
이것은 처음에는 직관적이지 않은 것처럼 보일 수 있지만 다음을 고려하십시오. 모든 코드를 단일 메서드에 넣고 공유 메모리 청크를 사용하여 임시 값을 저장하고 상당한 양의 점프 명령을 사용하면 컴퓨터는 문제가 없습니다. 게다가, 최적화 단계의 컴파일러는 때때로 이것을 합니다. 그러나 인간의 두뇌는 이러한 접근 방식을 쉽게 처리하지 못합니다.
함수형 프로그래밍은 표현 방식으로 강력한 유형을 사용하여 부작용 없이 순수 함수를 작성하도록 합니다. 그렇게 하면 함수가 생성하는 것은 반환 값뿐이기 때문에 함수에 대해 추론하는 것이 훨씬 쉽습니다. Programming Throwdown 팟캐스트 에피소드 Functional Programming With Adam Gordon Bell은 기본적인 이해를 돕는 데 도움이 되며 Corecursive 에피소드 God's Programming Language With Philip Wadler 및 Category Theory With Bartosz Milewski를 계속할 수 있습니다. 마지막 두 가지는 프로그래밍에 대한 나의 인식을 크게 풍부하게 했습니다.
테스트 주도 개발
TDD를 마스터하는 것이 좋습니다. 배우는 가장 좋은 방법은 연습하는 것입니다. 문자열 계산기 Kata는 코드 kata로 연습할 수 있는 좋은 방법입니다. kata를 마스터하는 것은 시간이 걸리겠지만 궁극적으로 TDD의 개념을 완전히 흡수할 수 있게 해줄 것이며, 이는 작업하기 좋고 테스트할 수 있는 잘 구조화된 코드를 만드는 데 도움이 될 것입니다.
한 가지 주의 사항: TDD가 프로그래밍하는 유일한 올바른 방법이라고 주장하는 TDD 순수주의자를 볼 수 있습니다. 제 생각에는 도구 상자에 있는 또 다른 유용한 도구일 뿐 그 이상은 아닙니다.
때로는 모듈과 프로세스를 서로 관련하여 조정하는 방법을 확인해야 하고 어떤 데이터와 서명을 사용해야 할지 모를 때가 있습니다. 이러한 경우 컴파일될 때까지 코드를 작성한 다음 문제를 해결하고 기능을 디버그하기 위한 테스트를 작성하십시오.
다른 경우에는 원하는 입력과 출력을 알고 있지만 복잡한 논리로 인해 구현을 올바르게 작성하는 방법을 모릅니다. 이러한 경우에는 완벽한 구현에 대해 생각하는 데 시간을 보내는 것보다 TDD 절차를 따라 시작하고 코드를 단계별로 작성하는 것이 더 쉽습니다.
7. 테스트를 단순하고 집중적으로 유지
불필요한 방해 없이 깔끔하게 정리된 코드 환경에서 작업하는 것이 즐겁습니다. 그렇기 때문에 필요할 때 리팩토링을 활용하여 SOLID, KISS 및 DRY 원칙을 테스트에 적용하는 것이 중요합니다.
때때로 저는 "모든 변경 사항에는 수십 개의 테스트를 수정해야 하기 때문에 심하게 테스트된 코드베이스에서 작업하는 것이 싫습니다."와 같은 의견을 듣습니다. 이는 집중되지 않고 너무 많은 테스트를 시도하는 테스트로 인해 발생하는 유지 관리가 많은 문제입니다. "한 가지만 잘 하라"는 원칙은 테스트에도 적용됩니다. 각 테스트는 상대적으로 짧고 하나의 개념만 테스트해야 합니다. "한 가지를 잘 테스트하라"는 것은 테스트당 하나의 어설션으로 제한되어야 한다는 것을 의미하지 않습니다. 사소하지 않고 중요한 데이터 매핑을 테스트하는 경우 수십 개의 어설션을 사용할 수 있습니다.
이 초점은 하나의 특정 테스트 또는 테스트 유형에 국한되지 않습니다. ERP 시스템의 데이터를 구조로 매핑하는 것과 같이 단위 테스트를 사용하여 테스트한 복잡한 논리를 처리하고 모의 ERP API에 액세스하고 결과를 반환하는 통합 테스트가 있다고 상상해 보십시오. 이 경우 통합 테스트에서 매핑을 다시 테스트하지 않도록 단위 테스트에서 이미 다루는 내용을 기억하는 것이 중요합니다. 일반적으로 결과에 올바른 식별 필드가 있는지 확인하는 것으로 충분합니다.
레고 브릭과 같은 구조의 코드와 집중 테스트를 통해 비즈니스 로직을 변경하는 데 어려움이 없어야 합니다. 변경 사항이 급진적이라면 파일 및 관련 테스트를 삭제하고 새 테스트로 새 구현을 수행하면 됩니다. 사소한 변경의 경우 일반적으로 1~3개의 테스트를 변경하여 새로운 요구 사항을 충족하고 논리를 변경합니다. 테스트를 변경하는 것은 괜찮습니다. 이 관행을 복식 부기라고 생각할 수 있습니다.
단순성을 달성하는 다른 방법은 다음과 같습니다.
- 테스트 파일 구조화, 테스트 콘텐츠 구조화(일반적으로 Arrange-Act-Assert 구조) 및 테스트 이름 지정에 대한 규칙을 제시합니다. 그런 다음 가장 중요한 것은 이러한 규칙을 일관되게 따르는 것입니다.
- "요청 준비"와 같은 방법으로 큰 코드 블록을 추출하고 반복되는 작업에 대한 도우미 기능을 만듭니다.
- 테스트 데이터 구성을 위한 빌더 패턴 적용
- (통합 테스트에서) 기본 앱에서 사용하는 것과 동일한 DI 컨테이너를 사용하여 수동으로 종속성을 생성하지 않고도 모든 인스턴스화가
TestServices.Get()만큼 간단합니다. 그렇게 하면 이미 유용한 도우미가 있기 때문에 새 테스트를 읽고, 유지 관리하고, 작성하기가 쉽습니다.
테스트가 너무 복잡해지고 있다고 생각되면 멈추고 생각하십시오. 모듈이나 테스트를 리팩토링해야 합니다.
8. 도구를 사용하여 삶을 더 쉽게 만드십시오
테스트하는 동안 많은 지루한 작업에 직면하게 됩니다. 예를 들어 테스트 환경 또는 데이터 개체 설정, 종속성에 대한 스텁 및 모의 구성 등이 있습니다. 운 좋게도 모든 성숙한 기술 스택에는 이러한 작업을 훨씬 덜 지루하게 만드는 몇 가지 도구가 포함되어 있습니다.
아직 작성하지 않았다면 처음 100개의 테스트를 작성한 다음 시간을 투자하여 반복적인 작업을 식별하고 기술 스택에 대한 테스트 관련 도구에 대해 알아보는 것이 좋습니다.
영감을 얻기 위해 사용할 수 있는 몇 가지 도구는 다음과 같습니다.
- 테스트 러너. 간결한 구문과 사용 편의성을 찾으십시오. 내 경험에 따르면 .NET의 경우 xUnit을 권장합니다(NUnit도 확실한 선택이지만). JavaScript 또는 TypeScript의 경우 Jest를 사용합니다. 도구와 도전 과제가 진화하기 때문에 작업과 사고 방식에 가장 적합한 것을 찾으십시오.
- 모의 라이브러리 . 인터페이스와 같은 코드 종속성에 대한 낮은 수준의 모의가 있을 수 있지만 웹 API 또는 데이터베이스에 대한 높은 수준의 모의도 있습니다. JavaScript 및 TypeScript의 경우 Jest에 포함된 낮은 수준의 모의는 괜찮습니다. .NET용. NSubstitute도 훌륭하지만 Moq를 사용합니다. Web API mock의 경우 WireMock.NET을 즐겨 사용합니다. API 대신 문제를 해결하고 응답 처리를 디버그하는 데 사용할 수 있습니다. 또한 자동화된 테스트에서 매우 안정적이고 빠릅니다. 데이터베이스는 메모리 내 대응물을 사용하여 조롱될 수 있습니다. .NET의 EfCore는 이러한 옵션을 제공합니다.
- 데이터 생성 라이브러리 . 이러한 유틸리티는 데이터 개체를 임의의 데이터로 채웁니다. 예를 들어, 빅 데이터 전송 개체의 몇 가지 필드에만 관심이 있는 경우에 유용합니다(그렇다면 매핑 정확성만 테스트하려는 경우). 테스트용으로 사용할 수도 있고 양식에 표시하거나 데이터베이스를 채우기 위해 임의의 데이터로 사용할 수도 있습니다. 테스트 목적으로 .NET에서 AutoFixture를 사용합니다.
- UI 자동화 라이브러리 . 이들은 자동화된 테스트를 위한 자동화된 사용자입니다. 앱을 실행하고, 양식을 작성하고, 버튼을 클릭하고, 레이블을 읽는 등의 작업을 수행할 수 있습니다. 앱의 모든 요소를 탐색하기 위해 좌표 또는 이미지 인식을 통한 클릭을 처리할 필요가 없습니다. 주요 플랫폼에는 유형, 식별자 또는 데이터별로 필요한 요소를 찾는 도구가 있으므로 재설계할 때마다 테스트를 변경할 필요가 없습니다. 그것들은 강력하기 때문에 일단 당신과 CI를 위해 작동하게 만들면(때로는 당신 의 머신 에서만 작동한다는 것을 알게 됨), 그들은 계속 작동할 것입니다. 저는 .NET용 FlaUI와 JavaScript 및 TypeScript용 Cypress를 즐겨 사용합니다.
- 어설션 라이브러리 . 대부분의 테스트 러너에는 어설션 도구가 포함되어 있지만 Fluent Assertions for .NET과 같이 더 깔끔하고 읽기 쉬운 구문을 사용하여 복잡한 어설션을 작성하는 데 독립 도구가 도움이 되는 경우가 있습니다. 나는 특히 항목의 순서나 메모리의 주소에 관계없이 컬렉션이 동일하다고 주장하는 기능을 좋아합니다.
흐름이 당신과 함께하기를
행복은 Flow: Psychology of Optimal Experience 책에 자세히 설명되어 있는 소위 "흐름" 경험과 밀접하게 연결되어 있습니다. 이러한 흐름 경험을 달성하려면 명확한 목표가 있는 활동에 참여하고 진행 상황을 볼 수 있어야 합니다. 작업은 자동화된 테스트가 이상적인 즉각적인 피드백을 제공해야 합니다. 또한 도전과 기술 간의 균형을 유지해야 하며 이는 모든 개인에게 달려 있습니다. 특히 TDD를 사용하여 접근할 때 테스트는 지침을 제공하고 자신감을 심어주는 데 도움이 될 수 있습니다. 통과한 각 테스트는 진행 상황의 지표가 되는 특정 목표를 설정하는 데 도움이 됩니다.
테스트에 대한 올바른 접근 방식은 사용자를 더 행복하고 생산적으로 만들 수 있으며 테스트는 소진 가능성을 줄입니다. 핵심은 테스트를 미래의 코드를 보장하기 위한 부담스러운 단계가 아니라 일상적인 개발 루틴에서 도움이 될 수 있는 도구(또는 도구 세트)로 보는 것입니다.
테스트는 소프트웨어 엔지니어가 작업 방식을 개선하고 최상의 결과를 제공하며 시간을 최적으로 사용할 수 있도록 하는 프로그래밍의 필수 부분입니다. 아마도 훨씬 더 중요한 것은 테스트를 통해 개발자가 작업을 더 즐길 수 있어 사기와 동기 부여가 향상될 수 있다는 것입니다.
