02 Test
02 Test
1. Test
1. Test
- 소프트웨어가 의도한 대로 동작하는지 검증과 확인하는 활동
- “시스템이 올바르게 작동하는가?”를 확인하는 절차로, 단순히 오류를 찾는 걸 넘어서 품질을 보장하는 과정
2. 주요목적
구분 | 설명 | 기대 효과 |
---|---|---|
결함 조기 발견 | 개발 초기 단계에서 오류를 찾아 수정 | 수정 비용 절감, 안정적 개발 주기 확보 |
품질 보증 (Quality Assurance) | 기능이 명세와 요구사항에 맞게 동작하는지 확인 | 신뢰성, 일관성 확보 |
리팩토링 안전망 제공 | 코드 변경 시 기존 기능이 깨지지 않도록 보장 | 유지보수성 향상, 배포 리스크 감소 |
문서 대체 역할 | 테스트 코드가 코드의 의도를 설명하는 “살아있는 명세서”로 작동 | 코드 이해도 향상, 협업 효율성 증가 |
자동화 기반 품질 관리 | 반복 가능한 테스트로 품질을 지속적으로 검증 | 지속적 통합(CI) 및 배포 자동화 지원 |
3. 검증(Verification)과 확인(Validation)
구분 | 검증(Verification) | 확인(Validation) |
---|---|---|
의미 | “설계대로 잘 만들었는가 (개발자 관점)” | “고객이 원하는 걸 만든 건가 (사용자 관점)” |
초점 | 명세서/요구사항과의 일치 | 실제 사용자 요구와의 일치 |
시점 | 개발 과정 중 (단위, 통합 등) | 개발 완료 후 (인수, 시스템 테스트 등) |
예시 | 코드 리뷰, 단위 테스트 | 사용자 테스트, 프로토타입 검증 |
4. 의의
- 테스트의 본질적인 가치
- 개발 효율성 향상 (오류를 미리 잡아 반복 작업 감소)
- 팀 커뮤니케이션 개선 (명확한 기대 동작 정의)
- 시스템 안정성 확보 (배포 이후 장애 리스크 최소화)
2. 테스트 구분
1. 단위 테스트 (Unit Test)
- 정의: 가장 작은 단위(함수, 메서드, 클래스 등)를 독립적으로 검증하는 테스트
- 목적: 코드의 개별 모듈이 설계대로 동작하는지 확인
- 특징
- 빠르고 실행 비용이 낮음
- 테스트 격리(isolation) 중요
- Mock, Stub 등 Test Double 자주 사용
- 실무 활용 예: 비즈니스 로직 검증, 유틸리티 메서드 검증
2. 통합 테스트 (Integration Test)
- 정의: 여러 모듈이나 시스템이 함께 동작하는지 검증하는 테스트
- 목적: 모듈 간 인터페이스와 데이터 흐름이 올바른지 확인
- 특징
- 단위 테스트보다 실행 비용이 높음
- 실제 환경과 비슷하게 구성하여 테스트 진행
- 데이터베이스, API, 외부 시스템 연동 포함 가능
- 실무 활용 예: 서비스-DB 통합 검증, 외부 API 연동 테스트
3. 회귀 테스트 (Regression Test)
- 정의: 기존 기능이 변경·추가 후에도 정상 동작하는지 검증하는 테스트
- 목적: 코드 변경에 따른 부작용(Regression)이 발생하지 않았는지 확인
- 특징
- 자동화 테스트로 구현되는 경우 많음
- CI/CD 파이프라인에 통합 가능
- 실무 활용 예: 새로운 기능 추가 후 전체 기능 점검, 버그 수정 후 전체 영향 검증
4. 범위/목적/시점
테스트 종류 | 범위 | 목적 | 실행 시점 |
---|---|---|---|
단위 테스트 | 개별 모듈 | 로직 검증 | 개발 초기 단계 |
통합 테스트 | 모듈 간 결합 | 인터페이스 검증 | 개발 중간 단계 |
회귀 테스트 | 전체 시스템 | 변경 영향 검증 | 변경 후 / 배포 전 |
3. 테스트기법
1. 화이트박스 테스트 (White-box Testing)
- 정의: 테스트 설계자가 시스템 내부 구조, 로직, 코드 구현 방식을 알고 진행하는 테스트
- 목적: 코드의 내부 동작과 흐름이 설계대로 작동하는지 검증
- 특징
- 코드 커버리지를 중요하게 다룸 (문장, 분기, 조건 커버리지 등)
- 단위 테스트에 주로 사용
- 내부 로직 변경이 테스트 설계에 영향 줌
- 실무 활용 예: 알고리즘 로직 검증, 조건 분기 처리 테스트
2. 블랙박스 테스트 (Black-box Testing)
- 정의: 시스템 내부 구조를 고려하지 않고, 입력과 출력만을 기반으로 테스트를 설계하는 기법
- 목적: 요구사항·명세서에 맞게 기능이 동작하는지 검증
- 특징
- 내부 구조를 몰라도 설계 가능
- 주로 통합 테스트, 시스템 테스트, 인수 테스트에서 사용
- 테스트 케이스는 기능 명세 기반
- 실무 활용 예: API 기능 검증, 사용자 인터페이스(UI) 테스트
3. 그레이박스 테스트 (Gray-box Testing)
- 정의: 화이트박스와 블랙박스 테스트의 중간 형태
- 목적: 일부 내부 구조를 알고 있으면서, 기능 중심으로 테스트 설계
- 특징
- 내부 구조를 일부 이해하지만 전체는 모름
- 보안 테스트, 통합 테스트에서 활용
- 실무 활용 예: 데이터 흐름 테스트, 보안 취약점 점검
4. 테스트 비교표
기법 | 개발 주체 | 내부 구조 인지 여부 | 주요 사용 단계 | 장점 | 단점 |
---|---|---|---|---|---|
화이트박스 | 개발자 | 내부 구조 O | 단위 테스트 | 코드 커버리지 높음, 논리 오류 발견 용이 | 설계 변경 시 테스트 수정 필요 |
블랙박스 | QA팀, 테스터, 사용자 | 내부 구조 X | 통합/시스템/인수 테스트 | 요구사항 기반, 사용자 관점 테스트 가능 | 내부 결함 발견 어려움 |
그레이박스 | 개발자 + QA팀 | 부분 인지 | 보안·통합 테스트 | 장점 혼합, 효율적 | 설계 정보 필요 |
4. 개발 방식
1. 개발 방식 비교
구분 | TDD (Test Driven Development) | BDD (Behavior Driven Development) |
---|---|---|
구분 | 전통적인 방식 | TDD의 확장형 |
초점 | 코드 단위 로직 검증 | 사용자 관점 행위 검증 |
테스트 형태 | 단위 테스트 | 기능/행위 테스트 |
참여 주체 | 개발자 | 개발자 + QA + 비즈니스 담당자 |
설계 단위 | 메서드, 클래스 | 시나리오, 사용자 스토리 |
2. 테스트 주도 개발 (TDD, Test Driven Development)
1. 테스트주도개발
- 코드를 작성하기 전에 테스트를 먼저 작성하는 개발 방식
- 기능 구현 전에 “어떤 동작을 해야 하는지”를 테스트 코드로 정의하고, 그 테스트를 통과시키는 코드를 작성하는 흐름
2. TDD의 핵심 원칙
- 테스트 코드 먼저 작성: 요구사항을 테스트 케이스로 구현
- 작게, 자주 개발: 기능 단위를 작게 나눠 테스트와 구현 반복
- 리팩토링 포함: 코드 품질 향상 및 유지보수성을 위해 지속적으로 리팩토링
3. Red–Green–Refactor 사이클
- Red 단계 (실패하는 테스트 작성)
- 새로운 기능을 구현하기 전에 테스트 코드를 먼저 작성
- 아직 기능이 구현되지 않았으므로 테스트는 실패 상태가 됨 → Red
- 목적: 어떤 기능이 필요하고, 그 기능이 어떻게 동작해야 하는지 명확히 정의
- 예시
1 2 3 4
@Test void testAdd() { assertEquals(4, calculator.add(2, 2)); }
→ 이 테스트는 아직 calculator.add() 기능이 없거나 잘못 구현됐으므로 실패함
- Green 단계 (테스트를 통과하는 최소 코드 구현)
- 실패한 테스트를 통과시키기 위해 최소한의 코드를 작성
- 목적: 빠르게 기능을 구현하고 테스트를 통과시키는 것
- 핵심: 구현은 최소한으로, 복잡한 최적화는 다음 단계에서 진행
- 예시
1 2 3
int add(int a, int b) { return a + b; }
→ 테스트가 성공 → Green 상태
- Refactor 단계 (코드 리팩토링)
- 기능이 정상 동작하는 상태에서 코드 품질을 개선
- 목적: 중복 제거, 가독성 향상, 유지보수성 확보
- 중요한 점: 테스트를 통과시키는 상태를 유지하면서 리팩토링
- 예시: 변수명 개선, 메서드 구조 개선, 불필요한 코드 제거
3. 행위 중심 개발 (BDD, Behavior Driven Development)
1. 행위 중심 개발
- 행위(Behavior) 중심으로 테스트를 설계하고 개발하는 방식
- “시스템이 어떻게 동작해야 하는가”를 사용자 관점에서 정의하고 검증하는 방법론
2. 핵심원칙
- 행위 중심: 시스템이 어떻게 동작해야 하는지 사용자 관점에서 정의
- 비즈니스 언어 사용: 이해관계자가 쉽게 이해할 수 있는 자연어로 테스트 설계
- Given–When–Then 구조: 테스트 시나리오를 일관성 있게 작성
- 협업 중심: 개발자·QA·비즈니스 담당자가 함께 시나리오 설계
- 문서화: 테스트 시나리오 자체가 살아있는 문서 역할
3. Given-when-then 사이클
- Given: 주어진 상황 (상태, 조건)
- When: 수행할 행동
- Then: 기대되는 결과
- 예시
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
@Test public void testLogin() { // Given: 초기 상태 설정 LoginPage loginPage = new LoginPage(); loginPage.open(); // When: 행동 실행 loginPage.enterUsername("testUser"); loginPage.enterPassword("securePassword"); loginPage.clickLogin(); // Then: 기대 결과 검증 HomePage homePage = new HomePage(); assertTrue(homePage.isDisplayed()); }
5. 테스트 더블 (Test Double)
1. 테스트 더블
- 테스트할 때 실제 객체 대신 사용하는 가짜 객체
- 테스트를 독립적으로 만들고, 외부 의존성을 제거하는 것
2. 테스트 더블 종류
1. 종류 구분
종류 | 설명 | 사용 목적 |
---|---|---|
Stub | 미리 정의된 데이터를 반환하는 간단한 객체 | 특정 입력에 대해 정해진 값을 반환하도록 설정 |
Mock | 메서드 호출 여부, 횟수, 인자를 검증할 수 있는 객체 | 테스트 중 호출 여부와 동작 검증 |
Fake | 실제 동작하는 간단한 구현 객체 | 실제 로직의 단순화 버전, 성능 향상 또는 외부 시스템 대체 |
Spy | 실제 객체의 동작을 관찰할 수 있는 객체 | 메서드 호출 기록, 인자 값 추적 등 |
2. Stub
1
2
3
4
5
class UserServiceStub implements UserRepository {
public User findUserById(String id) {
return new User("testUser", "test@example.com"); // 항상 같은 값 반환
}
}
3. Mock (JUnit + Mockito 예시)
1
2
3
4
UserRepository mockRepo = Mockito.mock(UserRepository.class);
Mockito.when(mockRepo.findUserById("123")).thenReturn(new User("mockUser", "mock@example.com"));
verify(mockRepo).findUserById("123"); // 호출 여부 검증
4. Fake
1
2
3
4
5
6
7
8
9
10
11
class FakeUserRepository implements UserRepository {
private Map<String, User> data = new HashMap<>();
public User findUserById(String id) {
return data.get(id);
}
public void saveUser(User user) {
data.put(user.getId(), user);
}
}
5. SPY (JUnit + Mockito 예시)
1
2
3
4
5
List<String> list = new ArrayList<>();
List<String> spyList = Mockito.spy(list);
spyList.add("hello");
Mockito.verify(spyList).add("hello"); // 호출 여부 검증
6. 테스트 커버리지
1. 테스트 커버리지
- 코드 중 얼마나 많은 부분이 테스트됐는지 비율을 의미
- 테스트가 코드 전체에서 어느 정도를 확인했는지를 수치로 나타내는 것
2. 테스트커버리지 종류
종류 | 의미 |
---|---|
문장 커버리지 (Statement Coverage) | 코드의 각 문장이 최소 한 번 실행됐는지 확인 |
분기 커버리지 (Branch Coverage) | 조건문(if, switch 등)의 모든 분기가 실행됐는지 확인 |
조건 커버리지 (Condition Coverage) | 조건문 내부의 각 조건(참/거짓)이 모두 실행됐는지 확인 |
3. 스프링에서의 커버리지
도구 | 특징 |
---|---|
JaCoCo | 가장 널리 쓰이는 자바 코드 커버리지 도구 Maven/Gradle 연동 쉬움 문장/분기/조건 커버리지 제공 |
Cobertura | JaCoCo 이전의 오래된 도구 XML 보고서 제공 다소 업데이트 빈도 낮음 |
SonarQube | 코드 품질 분석 플랫폼 커버리지 측정 + 코드 품질 지표 제공 CI/CD 통합 가능 |
IntelliJ IDEA / Eclipse | IDE 내장 기능으로 커버리지 측정 빠른 시각화 설정 간단하지만 CI 환경에는 부적합 |
7. 테스트 자동화와 CI
1. 지속적 통합 (Continuous Integration, CI)
- 정의: 개발자들이 작성한 코드를 자주(보통 하루 여러 번) 중앙 저장소에 통합하고, 자동으로 빌드와 테스트를 실행하는 개발 방식.
- 목적: 통합 과정에서 발생하는 문제를 빠르게 발견하고 수정하기 위함.
- 특징
- 코드 통합 시마다 자동 빌드 + 테스트 실행
- 코드 품질 유지
- 버그 조기 발견
- 협업 효율 향상
- 예시 도구: Jenkins, GitHub Actions, GitLab CI, CircleCI
2. CI 동작과정
- 테스트 코드 작성
- 개발자가 프로젝트에 테스트 코드를 작성하고, 버전 관리 시스템(Git 등)에 등록
- 예: JUnit, Mockito, Spring Test 등으로 테스트 코드 구현
- 코드 커밋 & 푸시
- 개발자가 코드를 변경하고 저장소(GitHub, GitLab 등)에 푸시(push)
- CI 서버 자동 실행
- Jenkins, GitHub Actions, GitLab CI 같은 CI 도구가 커밋 푸시를 감지
- 자동으로 빌드(Build) + 테스트(Test) 실행
- 결과 보고
- 테스트 성공 여부를 알림
- 실패 시 개발자에게 바로 피드백
3. 지속적 배포 (Continuous Delivery / Continuous Deployment, CD)
- 정의: CI 과정 이후, 테스트를 통과한 코드를 자동으로 배포하거나 배포 준비 상태로 만드는 개발 방식.
- Continuous Delivery: 자동으로 배포 준비까지 진행하지만, 실제 배포는 수동 승인 필요
- Continuous Deployment: 모든 변경 사항을 자동으로 배포까지 진행
- 목적: 배포 과정을 자동화해 빠르고 안정적으로 소프트웨어를 사용자에게 전달하기 위함.
- 특징
- 테스트를 통과한 코드 자동 배포 또는 배포 준비
- 배포 속도 향상
- 배포 오류 최소화
- 운영 환경에서 빠른 피드백 가능
- 예시 도구: Jenkins, GitHub Actions, GitLab CI/CD, CircleCI, Spinnaker
4. 테스트 자동화 파이프라인
- 정의: CI 과정에서 테스트를 자동으로 실행하는 절차를 파이프라인(Workflow) 형태로 구성한 것.
- 목적: 사람이 개입하지 않아도 자동으로 테스트를 실행하고, 문제 발생 시 알림 제공.
- 흐름 예시
1 2
[코드 커밋] → [CI: 빌드 + 테스트 자동화] → [CD: 배포 자동화] ↑------------------- 파이프라인 -------------------↑
- 특징
- 테스트 자동 실행
- 실패 시 즉시 피드백
- 배포 전 품질 보증
- 테스트 커버리지, 코드 품질 보고서 자동 생성
- 예시 도구: Jenkins, Travis CI, GitHub Actions, CircleCI
This post is licensed under CC BY 4.0 by the author.