이전 글에서는 경계 값 테스트와 예외 처리를 통해 TDD 사이클을 완성했습니다.
이번 글에서는 Moq를 활용한 Mock 테스트와 DI(의존성 주입) 전략을 통해 테스트의 유연성을 높이는 방법을 다루겠습니다. 😎
이번 글에서는 다음 내용을 중점적으로 다룹니다:
✔️ Moq 프레임워크 설정 및 설치
✔️ Mock 객체 생성 및 주입
✔️ DI(Dependency Injection)를 통한 테스트 유연성 강화
✅ Mock 테스트란?
Mock 테스트는 실제 객체 대신 가짜 객체(Mock) 를 만들어 테스트하는 기법입니다.
Mock 객체를 사용하면 외부 의존성에 영향을 받지 않고 독립적으로 단위 테스트를 수행할 수 있습니다.
🎯 Mock 테스트가 필요한 경우
- 데이터베이스 호출이 필요한 경우
- 외부 API 호출이 필요한 경우
- 네트워크 연결이 필요한 경우
- 파일 시스템 접근이 필요한 경우
👉 Mock 객체를 사용하면 테스트 속도 향상 및 테스트 독립성 강화가 가능합니다.
✅ Moq 프레임워크란?
Moq는 C#에서 가장 널리 사용되는 Mock 프레임워크입니다.
테스트 대상 객체의 동작을 쉽게 시뮬레이션할 수 있습니다.
Moq 주요 기능:
✔️ 메서드 호출 시 동작 정의
✔️ 반환 값 설정
✔️ 호출 횟수 검증
✔️ 예외 발생 시뮬레이션
🚀 1. Moq 설치하기
🛠️ NuGet 패키지 설치
NuGet 패키지 관리자에서 다음 명령어 입력:
dotnet add package Moq
또는 Visual Studio에서 다음과 같이 설치합니다:
- 도구 → NuGet 패키지 관리자 → 패키지 관리
- Moq 검색 후 설치
🚀 2. DI(의존성 주입) 적용하기
🔧 Calculator 클래스를 인터페이스로 리팩토링
의존성 주입을 사용하려면 인터페이스를 정의해야 합니다.
기존 Calculator 클래스를 다음과 같이 수정합니다.
✅ 인터페이스 정의 (ICalculator.cs)
public interface ICalculator
{
int Add(int a, int b);
int Subtract(int a, int b);
int Multiply(int a, int b);
int Divide(int a, int b);
}
✅ Calculator 클래스 수정 (Calculator.cs)
public class Calculator : ICalculator
{
public int Add(int a, int b) => a + b;
public int Subtract(int a, int b) => a - b;
public int Multiply(int a, int b) => a * b;
public int Divide(int a, int b)
{
if (b == 0)
throw new DivideByZeroException("Cannot divide by zero.");
return a / b;
}
}
🚀 3. Moq를 사용한 Mock 테스트 작성하기
이제 Moq를 활용해 Mock 객체를 만들어 보겠습니다.
ICalculator 인터페이스를 기반으로 Mock 객체를 생성하고 동작을 정의합니다.
✅ 기본 Mock 테스트 코드 작성
CalculatorTests.cs 파일 수정
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
namespace TDDExample.Tests
{
[TestClass]
public class CalculatorTests
{
private Mock<ICalculator> _mockCalculator;
[TestInitialize]
public void Setup()
{
_mockCalculator = new Mock<ICalculator>();
}
[TestMethod]
public void Add_ShouldReturnSumOfTwoNumbers()
{
// Arrange
_mockCalculator.Setup(m => m.Add(2, 3)).Returns(5);
// Act
int result = _mockCalculator.Object.Add(2, 3);
// Assert
Assert.AreEqual(5, result);
_mockCalculator.Verify(m => m.Add(2, 3), Times.Once());
}
}
}
✅ 테스트 코드 설명
- Mock<ICalculator> → Moq 프레임워크에서 Mock 객체 생성
- Setup → Mock 객체의 동작 설정
- Returns → 동작 수행 시 반환값 설정
- Verify → 메서드 호출 횟수 검증
🚀 4. 복잡한 동작 정의하기
복잡한 로직에서도 Mock 객체를 통해 동작을 세밀하게 정의할 수 있습니다.
예를 들어 다음과 같은 동작을 정의할 수 있습니다:
✅ 다양한 입력 값 처리
_mockCalculator.Setup(m => m.Add(It.IsAny<int>(), It.IsAny<int>()))
.Returns((int x, int y) => x + y);
- It.IsAny<int>() → 어떤 정수 입력 값에도 반응
- 람다식을 사용해 입력 값에 따라 동작 정의
✅ 예외 발생 시뮬레이션
_mockCalculator.Setup(m => m.Divide(It.IsAny<int>(), 0))
.Throws(new DivideByZeroException());
- 특정 입력 값에서 예외 발생하도록 설정
- Divide 메서드가 0으로 나눌 때 예외 발생
✅ 호출 횟수 검증
_mockCalculator.Verify(m => m.Add(2, 3), Times.Once());
- Times.Once() → 한 번만 호출되었는지 검증
- 호출 횟수에 따라 다른 값 반환 가능
🚀 5. 의존성 주입 적용하기
✅ CalculatorService 클래스 생성
실제 서비스 클래스에서 ICalculator를 의존성 주입받아 사용합니다.
public class CalculatorService
{
private readonly ICalculator _calculator;
public CalculatorService(ICalculator calculator)
{
_calculator = calculator;
}
public int CalculateSum(int a, int b)
{
return _calculator.Add(a, b);
}
}
✅ 테스트에서 DI 적용
[TestMethod]
public void CalculateSum_ShouldReturnSumUsingMock()
{
// Arrange
_mockCalculator.Setup(m => m.Add(2, 3)).Returns(5);
var service = new CalculatorService(_mockCalculator.Object);
// Act
int result = service.CalculateSum(2, 3);
// Assert
Assert.AreEqual(5, result);
}
✅ 리팩토링 단계
- 인터페이스 정의 → 코드 유연성 강화
- Mock 객체 → 외부 의존성 제거 및 테스트 독립성 확보
- DI → 코드 재사용성 및 테스트 효율성 강화
👉 테스트 코드 최종 버전 ✅
🎯 마무리 및 다음 단계
지금까지 다음 내용을 완성했습니다:
✔️ Moq를 사용한 Mock 객체 생성 및 활용
✔️ 다양한 입력 값 및 호출 횟수 검증
✔️ 의존성 주입을 통한 테스트 유연성 강화
다음 글에서는 TDD에서 발생할 수 있는 문제와 해결 전략을 다루겠습니다.
👉 다음 글: C# MSTest에서 발생하는 테스트 문제 및 해결 전략 (5편)