Test(테스트)는 소프트웨어 개발 과정에서 작성한 코드가 예상대로 동작하는지 확인하고, 소프트웨어의 품질을 보장하기 위해 수행하는 활동을 의미합니다. 테스트는 소프트웨어의 기능적 요구 사항과 비기능적 요구 사항을 검증하여 잠재적인 오류, 결함, 또는 비효율성을 발견하고 해결하는 데 중요한 역할을 합니다.
테스트 중에 실제 객체 대신에 가짜 객체(Mock Object)를 사용하는 것을 의미합니다. 모의 객체는 의존성을 대체하며, 테스트 대상 클래스가 의존성으로 인해 테스트하기 어려운 경우에 주로 사용됩니다.
Stubbing은 모의 객체의 특정 메서드 호출에 대해 미리 정의된 동작이나 값을 반환하도록 설정하는 것을 의미합니다. 스터빙을 통해 테스트 중에 메서드가 호출될 때 어떻게 동작할지 미리 지정할 수 있습니다.
스파이는 실제 객체의 일부 메서드를 호출하면서도, 특정 메서드의 동작을 제어하거나 관찰하고 싶을 때 사용합니다. 스파이된 객체는 실제 객체를 감싸는 프록시로서, 실제 객체의 메서드를 호출할 수 있는 상태를 유지합니다. 스파이는 기본적으로 실제 객체의 동작을 수행하지만, 필요한 경우 특정 메서드를 모킹하거나 스터빙하여 동작을 변경할 수 있습니다.
| 특징 | Mocking (모킹) | Stubbing (스터빙) |
|---|---|---|
| 대상 | 객체 | 메소드 |
| 목적 | 실제 객체를 대체하여, 테스트 환경에서 독립적으로 테스트를 수행할 수 있게 함 | 테스트 환경에서 특정 메서드 호출에 대한 동작을 제어하여, 테스트 대상의 행동을 예측 가능하게 만듬 |
| 동작 | 모의 객체는 호출된 메서드와 상호작용을 기록하고, 설정된 동작을 수행 | 스터빙된 메서드는 호출될 때 미리 정의된 동작을 수행하거나 값을 반환 |
| 특징 | Mocking (모킹) | Spying (스파이) |
|---|---|---|
| 대상 | 가짜 객체(모의 객체)를 사용하여 실제 객체를 대체 | 실제 객체를 감싸는 프록시 객체를 사용 |
| 기본 동작 | 모든 메서드 호출에 대해 아무 동작도 하지 않으며 기본값을 반환 | 실제 메서드 호출이 기본 동작 |
| 사용 목적 | 의존성을 대체하고 메서드 호출을 기록하거나 특정 반환 값을 지정하기 위해 사용 | 실제 객체의 동작을 일부 유지하면서, 특정 메서드의 동작을 모킹하거나 호출을 검증하기 위해 사용 |
| 제어 범위 | 모킹된 객체의 모든 메서드 호출을 제어할 수 있음 | 특정 메서드 호출을 모킹할 수 있지만, 나머지 메서드는 실제 동작을 유지 |
| 호출 기록 | 호출된 메서드와 호출 횟수를 기록할 수 있음 | 호출된 메서드와 호출 횟수를 기록할 수 있음 (실제 메서드 호출 포함) |
| 사용 예시 | 단위 테스트에서 의존성을 완전히 대체하여 테스트 격리를 유지하거나, 외부 시스템 호출을 방지하기 위해 사용 | 실제 객체의 상태를 유지하면서, 일부 메서드만 모킹하거나 호출 여부를 검증할 때 사용 |
Unit Test (단위 테스트)
Integration Test (통합 테스트)
├── Module Integration Test (모듈 통합 테스트)
├── Slice Test (슬라이스 테스트)
└── End-to-End (e2e) Test (종단 간 테스트)
System Test (시스템 테스트)
├── Functional System Test (기능 시스템 테스트)
└── Non-Functional System Test (비기능 시스템 테스트)
├── Performance Test (성능 테스트)
│ ├── Load Test (부하 테스트)
│ └── Stress Test (스트레스 테스트)
├── Security Test (보안 테스트)
└── Usability Test (사용성 테스트)
Regression Test (회귀 테스트)
Acceptance Test (인수 테스트)
하나의 모듈이 정상적으로 작동하는 것을 테스트합니다.
이 때 필요한 다른 모듈(의존성)은 mocking하여 테스트를 진행합니다.
private method는 직접적으로 테스트 하지 않으며 public, protected method를 통해 간접적으로 테스트합니다.
TDD에서는 단위테스트를 먼저 작성한 후 비지니스 코드를 작성하는 것을 주장합니다.
하지만 스타트업이나 SI처럼 비지니스 요구사항이 계속 변경되는 상황에서 Unit test 작성은 오히려 부작용을 초래할 수 있습니다.
이러한 경우 수학적인 알고리즘 같은 특정 부분만 단위 테스트로 작성하는 것이 좋을 수 있습니다.
두 개 이상의 모듈이 정상적으로 작동하는 것을 테스트합니다.
특정 계층만 따로 떼어 test하는 경우입니다.
데이터베이스 계층 검증, web layer 검증이 대표적인 예입니다.
Ex)
데이터베이스 계층: Repository -> Database -> Repository
Web layer: Filter -> Controller -> Controller advice or something -> ...
End to end test의 약어입니다.
시스템의 모든 구성 요소가 함께 올바르게 작동하는지, 전체 사용자 시나리오가 기대한 대로 진행되는지 확인하며 이 때 해당 동작에 필요한 모든 구성요소를 결합하여 테스트합니다.
프론트엔드까지의 결합된 것이 진정한 의미의 E2E 테스트이지만 백엔드에서는 api test를 E2E test라고 하기도 합니다.
System Test는 시스템의 전체 기능을 요구사항과 비교하여 검증하는 테스트입니다.
비기능적, 기능적 테스트로 나뉠 수 있습니다.
특정 부하 조건 하에서 시스템이 어떻게 동작하는지 평가합니다. 정상적인 동작을 목표로 합니다.
시스템의 최대 한계를 테스트합니다. 실패 이 후 동작에 대해 초점을 둡니다.
| 특징 | 스트레스 테스트 (Stress Test) | 부하 테스트 (Load Test) |
|---|---|---|
| 목적 | 시스템의 최대 한계를 초과한 상태에서의 성능 평가, 실패 지점 찾기 | 정상 운영 범위 내에서 시스템 성능 평가, 성능 병목 발견 |
| 부하 수준 | 시스템의 정상 운영 범위를 초과하는 부하 | 시스템의 예상 최대 정상 부하 |
| 결과의 중점 | 시스템이 어떻게 실패하거나 복구하는지를 평가 | 시스템의 응답 시간, 처리량, 자원 사용률 등의 성능 지표 측정 |
회귀 테스트(Regression Test)는 코드 변경 후 기존 기능이 여전히 올바르게 동작하는지를 확인하기 위해 사용하는 테스트 전략입니다. 회귀 테스트는 단위 테스트(Unit Test), 통합 테스트(Integration Test), 시스템 테스트(System Test)를 포함하여 모든 테스트 수준에서 수행될 수 있습니다.
특정 비즈니스 요구사항 또는 사용자 유즈케이스에 따라 구현되었는지 확인하는 테스트입니다.
비개발자(제품 소유자, QA)에 의해 주로 작성되며 필요에 따라 개발자가 비지니스 요구사항에 따라 작성되며 기능 단위 테스트, rest api와 같은 엔트포인트를 중심으로 검증합니다.
비개발자가 중심이 되어 시나리오를 작성하므로 테스트는 블랙박스테스트 형태를 띄는 것이 좋으며 비지니스 요구사항에 따라 앞서 말했던 모든 테스트가 인수테스트로 제공될 수 있습니다.
인수테스트는 비지니스 관점에서 특정 기능을 검증하는 것이 목표입니다. 흔히 API endpoint test가 주로 인수테스트가 되는 것 뿐이지 인수테스트는 여느 통합테스트, 유닛테스트가 포함 될 수 있습니다.
즉, 비지니스적인 관점에서 내부 서비스의 특정 구간은 확실하게 검증되어야 한다고 생각했을 때 해당 부분에 대한 단위테스트, 통합테스트가 인수테스트로 제공될 수 있으며 보안테스트, 부하테스트 등 역시 포함될 수 있습니다.
고객의 컴플레인으로 결제 로그를 제공 또는 확인해야 하는 절차가 중요하다고 할 때 인수테스트로 로그 수집, 결제 모듈의 단위 테스트와 통합테스트가 인수테스트로 제공될 수 있습니다.