이전 글에서는 Moq와 의존성 주입(DI) 을 활용해 테스트의 유연성을 강화하는 방법을 배웠습니다.
하지만 실전에서 TDD를 적용하다 보면 다양한 문제가 발생합니다.
이번 글에서는 테스트 실패 원인 분석과 해결 전략을 구체적인 예제와 함께 설명하겠습니다. 😎
이번 글에서는 다음 내용을 중점적으로 다룹니다:
✔️ 테스트 실패 원인 및 해결책
✔️ 비결정적(Flaky) 테스트 문제 해결
✔️ 성능 저하 문제 해결
✔️ 코드 리팩토링 전략
✅ 1. 테스트 실패 원인 및 해결책
테스트가 실패하는 원인은 대부분 다음과 같은 경우에 해당합니다:
🔴 1) 코드 오류
- 논리 오류 → 로직이 잘못 구현된 경우
- 잘못된 입력 값 처리 → 예외 처리 누락
✅ 해결책:
👉 디버거를 사용해 코드 동작 확인
👉 예외 처리 로직 강화
🔴 2) 테스트 코드 오류
- 테스트 메서드에서 잘못된 값 설정
- Assert 구문 오류
✅ 해결책:
👉 테스트 코드의 Arrange → Act → Assert 순서 점검
👉 Assert에서 기대 값과 실제 값이 정확한지 확인
🔴 3) 외부 의존성 문제
- DB, API 등 외부 시스템이 다운된 경우
- 파일 접근 문제 발생
✅ 해결책:
👉 Moq 등을 사용해 외부 의존성 제거
👉 네트워크 의존성 최소화
🔴 4) 시간 및 상태 의존성 문제
- 특정 시간대에서만 테스트가 실패하는 경우
- 상태 변화에 따라 테스트 결과가 달라지는 경우
✅ 해결책:
👉 고정된 값(Mock) 사용
👉 상태 의존성을 없애기 위해 독립적인 상태 유지
🚀 2. 비결정적(Flaky) 테스트 문제 해결
비결정적 테스트는 같은 코드에서도 매번 다른 결과가 나오는 경우입니다.
원인은 비동기 코드, 시간 의존성, 상태 의존성 등에서 발생할 수 있습니다.
❗ 문제 상황: 비동기 코드에서 테스트 실패
[TestMethod]
public async Task GetDataAsync_ShouldReturnCorrectData()
{
// Arrange
var service = new DataService();
// Act
var result = await service.GetDataAsync();
// Assert
Assert.AreEqual("ExpectedData", result);
}
👉 테스트가 간헐적으로 실패하는 경우 발생
✅ 해결책: 비동기 코드에서 Task.Delay 제거
- 비동기 테스트에서 Task.Delay 사용 시 실패 가능성 증가
- Task.Delay 대신 Mock 객체로 비동기 처리 시뮬레이션
✅ 수정된 코드 (Moq 적용)
[TestMethod]
public async Task GetDataAsync_ShouldReturnCorrectData()
{
// Arrange
var mockService = new Mock<IDataService>();
mockService.Setup(x => x.GetDataAsync())
.ReturnsAsync("ExpectedData");
// Act
var result = await mockService.Object.GetDataAsync();
// Assert
Assert.AreEqual("ExpectedData", result);
}
👉 비동기 테스트 문제 해결 ✅
🚀 3. 성능 저하 문제 해결
테스트 코드가 많아지면 테스트 실행 시간이 급격히 증가할 수 있습니다.
🔴 성능 저하 원인
- 과도한 반복 테스트
- DB 및 API 호출이 많음
- 파일 접근 시 발생하는 I/O 병목
✅ 해결책 1: 불필요한 반복 제거
테스트마다 동일한 상태 설정을 반복하면 성능이 저하됩니다.
👉 TestInitialize로 상태 설정 코드 통합
[TestClass]
public class CalculatorTests
{
private Calculator _calculator;
[TestInitialize]
public void Setup()
{
_calculator = new Calculator();
}
[TestMethod]
public void Add_ShouldReturnSumOfTwoNumbers()
{
int result = _calculator.Add(2, 3);
Assert.AreEqual(5, result);
}
}
✅ 해결책 2: DB 호출 제거 및 Mock 사용
테스트에서 DB나 API 호출이 필요하면 Mock 객체로 대체합니다.
[TestMethod]
public void GetData_ShouldReturnCorrectValue()
{
var mockService = new Mock<IDataService>();
mockService.Setup(x => x.GetData())
.Returns("TestData");
var result = mockService.Object.GetData();
Assert.AreEqual("TestData", result);
}
👉 테스트 실행 시간 단축 ✅
🚀 4. 코드 리팩토링 전략
테스트 코드의 품질을 높이려면 주기적으로 리팩토링이 필요합니다.
✅ 전략 1: 중복 코드 제거
테스트 코드에서 공통된 부분을 TestInitialize로 통합합니다.
✅ 전략 2: 테스트 케이스 명확화
테스트 이름에서 명확한 동작을 정의합니다.
👉 MethodName_ExpectedBehavior_InputCondition 형식 사용
예시:
✅ Add_ShouldReturnSum_WhenTwoPositiveNumbers
✅ Divide_ShouldThrowException_WhenDividingByZero
✅ 전략 3: 매직 넘버 제거
테스트 코드에서 의미 없는 숫자는 상수로 치환합니다.
const int ExpectedResult = 5;
[TestMethod]
public void Add_ShouldReturnSum_WhenTwoPositiveNumbers()
{
int result = _calculator.Add(2, 3);
Assert.AreEqual(ExpectedResult, result);
}
👉 테스트 코드 가독성 및 유지보수성 강화 ✅
🚀 5. 실제 발생 가능한 문제 및 해결 전략 요약
문제 유형 | 원인 | 해결책 |
코드 오류 | 로직 오류, 예외 처리 누락 | 디버그, 예외 처리 강화 |
테스트 코드 오류 | 잘못된 입력 값, Assert 오류 | Arrange-Act-Assert 점검 |
외부 의존성 문제 | API, DB 연결 문제 | Moq, DI로 의존성 제거 |
시간/상태 문제 | 특정 시간대, 상태 의존성 문제 | Mock 객체로 상태 시뮬레이션 |
성능 저하 | 반복 코드, 외부 호출 | TestInitialize, Mock 사용 |
🎯 마무리 및 다음 단계
지금까지 다음 내용을 완성했습니다:
✔️ TDD에서 발생할 수 있는 문제와 원인 분석
✔️ 성능 저하 및 Flaky 테스트 해결 전략
✔️ 리팩토링을 통한 테스트 코드 최적화
다음 글에서는 TDD 적용 사례와 최종 리뷰를 진행하겠습니다.
👉 다음 글: C# MSTest에서 TDD 적용 사례 및 결론 (6편)