테스트 가이드라인
1. 테스트 종류
1.1 단위 테스트
- 모듈의 개별 컴포넌트 테스트
- Jest 프레임워크 사용
- 모든 public 메서드 테스트 필수
- 의존성 모킹 필수
1.2 통합 테스트
- 모듈 간 상호작용 테스트
- 실제 데이터베이스 사용
- API 엔드포인트 테스트
- E2E 테스트 포함
2. 테스트 구조
2.1 파일 구조
module/
└── tests/
├── unit/
│ ├── controllers/
│ ├── services/
│ └── repositories/
└── integration/
├── api/
└── database/
2.2 네이밍 규칙
- 단위 테스트:
*.spec.ts - 통합 테스트:
*.e2e-spec.ts - 테스트 데이터:
*.fixture.ts
3. 테스트 작성 가이드라인
3.1 단위 테스트 예시
describe('UserService', () => {
let service: UserService;
let repository: MockType<Repository<User>>;
beforeEach(async () => {
const module = await Test.createTestingModule({
providers: [
UserService,
{
provide: getRepositoryToken(User),
useFactory: repositoryMockFactory,
},
],
}).compile();
service = module.get(UserService);
repository = module.get(getRepositoryToken(User));
});
it('should find a user by id', async () => {
const user = { id: 1, name: 'Test User' };
repository.findOne.mockReturnValue(user);
expect(await service.findOne(1)).toEqual(user);
expect(repository.findOne).toHaveBeenCalledWith({ where: { id: 1 } });
});
});
3.2 통합 테스트 예시
describe('User API (e2e)', () => {
let app: INestApplication;
beforeAll(async () => {
const moduleFixture = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleFixture.createNestApplication();
await app.init();
});
it('/v1/users (GET)', () => {
return request(app.getHttpServer())
.get('/v1/users')
.expect(200)
.expect('Content-Type', /json/);
});
afterAll(async () => {
await app.close();
});
});
4. 테스트 데이터 관리
4.1 테스트 픽스처
export const userFixture = {
validUser: {
id: 1,
email: 'test@example.com',
name: 'Test User',
},
invalidUser: {
id: 2,
email: 'invalid-email',
name: '',
},
};
4.2 데이터베이스 시딩
- 테스트 데이터베이스 사용
- 매 테스트 전 데이터 초기화
- 트랜잭션 롤백 활용
5. 모킹 가이드라인
5.1 모킹 대상
- 외부 API 호출
- 데이터베이스 연산
- 이메일 서비스
- 캐시 서비스
5.2 모킹 예시
const mockEmailService = {
sendEmail: jest.fn().mockResolvedValue(true),
};
const mockCacheService = {
get: jest.fn(),
set: jest.fn(),
del: jest.fn(),
};
6. 테스트 커버리지
6.1 커버리지 요구사항
- 전체 코드 커버리지: 80% 이상
- 모듈별 커버리지: 85% 이상
- 핵심 비즈니스 로직: 90% 이상
6.2 커버리지 측정
npm run test:cov
7. 테스트 자동화
7.1 CI/CD 파이프라인
- 커밋 시 단위 테스트 실행
- PR 시 전체 테스트 실행
- 배포 전 E2E 테스트 실행
7.2 테스트 환경
- 개발 환경
- 스테이징 환경
- 프로덕션 환경
8. 성능 테스트
8.1 부하 테스트
- 동시 사용자 처리
- 응답 시간 측정
- 리소스 사용량 모니터링
8.2 스트레스 테스트
- 최대 부하 테스트
- 장애 복구 테스트
- 메모리 누수 테스트
9. 테스트 우선순위 및 범위
9.1 테스트 우선순위
- 핵심 비즈니스 로직
- 도메인 서비스
- 애그리게이트
- 값 객체
- 응용 서비스
- 커맨드 핸들러
- 쿼리 핸들러
- 인프라스트럭처
- 리포지토리
- 외부 서비스 어댑터
- 인터페이스
- API 컨트롤러
- 미들웨어
9.2 도메인 테스트
- 도메인 규칙 검증
- 불변식 테스트
- 상태 변이 테스트
- 도메인 이벤트 테스트
9.3 경계 조건 테스트
- null/undefined 처리
- 빈 컬렉션
- 최대/최소값
- 특수 문자
- 날짜/시간 경계
10. 테스트 품질
10.1 테스트 가독성
- 명확한 테스트 설명
- Given-When-Then 패턴
- 의미 있는 변수명
- 중복 코드 제거
10.2 테스트 유지보수성
- 테스트 헬퍼 함수 활용
- 공통 설정 분리
- 테스트 데이터 중앙화
- 불필요한 의존성 제거
10.3 안티패턴 방지
- 너무 많은 모킹
- 불필요한 검증
- 깨지기 쉬운 테스트
- 테스트 간 의존성
11. 보안 테스트
11.1 인증/인가 테스트
describe('AuthGuard', () => {
it('should block unauthorized access', async () => {
const response = await request(app.getHttpServer())
.get('/protected-resource')
.expect(401);
});
it('should allow access with valid token', async () => {
const token = await generateValidToken();
const response = await request(app.getHttpServer())
.get('/protected-resource')
.set('Authorization', `Bearer ${token}`)
.expect(200);
});
});
11.2 입력 검증 테스트
describe('UserInput', () => {
it('should validate email format', async () => {
const invalidEmail = 'invalid-email';
const response = await request(app.getHttpServer())
.post('/users')
.send({ email: invalidEmail })
.expect(400);
});
it('should prevent XSS attacks', async () => {
const maliciousInput = '<script>alert("xss")</script>';
const response = await request(app.getHttpServer())
.post('/users')
.send({ name: maliciousInput })
.expect(400);
});
});
12. 비동기 테스트
12.1 이벤트 핸들링
describe('UserEventHandler', () => {
it('should handle user created event', async () => {
// Given
const event = new UserCreatedEvent({ id: 1, email: 'test@example.com' });
// When
await eventHandler.handleUserCreated(event);
// Then
expect(emailService.sendWelcomeEmail).toHaveBeenCalledWith('test@example.com');
});
});
12.2 타임아웃 처리
describe('LongRunningOperation', () => {
it('should complete within timeout', async () => {
const operation = new LongRunningOperation();
await expect(operation.execute()).resolves.toEqual(
expect.anything()
).rejects.toThrow('Operation timed out');
}, 5000); // 5초 타임아웃
});
변경 이력
| 버전 | 날짜 | 작성자 | 변경 내용 |
|---|---|---|---|
| 0.1.0 | 2025-03-16 | bok@weltcorp.com | 최초 작성 |
| 0.2.0 | 2025-03-26 | bok@weltcorp.com | 테스트 우선순위, 품질, 보안, 비동기 테스트 섹션 추가 |