이전 글에서는 외부 API와 데이터베이스 호출을 Moq을 활용하여 Mocking하는 방법을 소개했습니다. 하지만 일부 상황에서는 Mocking이 아닌, 실제 테스트용 데이터베이스 및 네트워크 환경에서 테스트를 수행해야 합니다. 특히 레거시 시스템에서는 의존성을 완전히 분리하기 어려워 Integration Test(통합 테스트)가 필수적인 경우가 많습니다.
이번 글에서는 실제 테스트 환경에서 테스트를 수행할 수 있도록, 테스트용 데이터베이스 구성과 통합 테스트 전략을 단계별로 설명합니다.
🧩 1. 통합 테스트가 필요한 이유
TDD의 핵심은 작은 단위의 코드(단위 테스트)를 통해 신뢰할 수 있는 프로그램을 만드는 것입니다. 그러나 실제 운영 환경에서는 여러 구성 요소가 유기적으로 작동합니다.
❗ Mock만으로는 테스트가 어려운 경우:
- 외부 시스템(ERP, 대외 API 등)과의 실제 통신 확인
- 대량 데이터 Insert/Delete/Update 시 성능 및 트랜잭션 확인
- DB 스키마, 인덱스, 제약 조건 유효성 검증
👉 이럴 땐 실제 테스트 환경에서 실행하는 통합 테스트(Integration Test)가 필요합니다.
🛠️ 2. 테스트 데이터베이스 준비하기 (.NET Framework 기준)
✅ 운영환경과 분리된 테스트 전용 DB를 구축해야 합니다.
- SQL Server 기준 구성
TestDB
라는 이름으로 테스트 전용 데이터베이스 생성- 운영 DB와 동일한 스키마 복제 (단, 테스트용 더미 데이터만 입력)
- 테스트 전용 계정 생성 (
test_user
, 최소 권한 부여)
- App.config에 연결 문자열 설정
<connectionStrings>
<add name="TestDbConnection"
connectionString="Server=localhost;Database=TestDB;User Id=test_user;Password=test_pw;"
providerName="System.Data.SqlClient"/>
</connectionStrings>
🔄 3. 테스트 전/후 데이터 초기화 전략
✅ 테스트의 독립성과 반복성을 보장해야 합니다.
MSTest의 TestInitialize
, TestCleanup
사용
[TestClass]
public class DatabaseIntegrationTests
{
private SqlConnection _connection;
[TestInitialize]
public void Init()
{
_connection = new SqlConnection(ConfigurationManager.ConnectionStrings["TestDbConnection"].ConnectionString);
_connection.Open();
var command = new SqlCommand("DELETE FROM Orders", _connection);
command.ExecuteNonQuery();
}
[TestCleanup]
public void Cleanup()
{
_connection.Close();
}
}
👉 테스트 실행 전마다 데이터 초기화 → 테스트 간 간섭 방지
🧪 4. 실제 통합 테스트 코드 예제
다음은 테스트 DB에 연결해 주문 데이터를 저장하고 조회하는 Integration Test 예제입니다.
📌 대상 클래스: OrderRepository
public class OrderRepository
{
private readonly string _connectionString;
public OrderRepository(string connectionString)
{
_connectionString = connectionString;
}
public void SaveOrder(Order order)
{
using (var conn = new SqlConnection(_connectionString))
{
conn.Open();
var command = new SqlCommand("INSERT INTO Orders (OrderId, TotalAmount) VALUES (@id, @amount)", conn);
command.Parameters.AddWithValue("@id", order.OrderId);
command.Parameters.AddWithValue("@amount", order.TotalAmount);
command.ExecuteNonQuery();
}
}
public Order GetOrder(int orderId)
{
using (var conn = new SqlConnection(_connectionString))
{
conn.Open();
var command = new SqlCommand("SELECT OrderId, TotalAmount FROM Orders WHERE OrderId = @id", conn);
command.Parameters.AddWithValue("@id", orderId);
using (var reader = command.ExecuteReader())
{
if (!reader.Read()) return null;
return new Order
{
OrderId = reader.GetInt32(0),
TotalAmount = reader.GetDecimal(1)
};
}
}
}
}
📌 테스트 코드
[TestClass]
public class OrderRepositoryTests
{
private OrderRepository _repository;
[TestInitialize]
public void Setup()
{
var connStr = ConfigurationManager.ConnectionStrings["TestDbConnection"].ConnectionString;
_repository = new OrderRepository(connStr);
using (var conn = new SqlConnection(connStr))
{
conn.Open();
new SqlCommand("DELETE FROM Orders", conn).ExecuteNonQuery();
}
}
[TestMethod]
public void SaveOrder_And_GetOrder_Returns_Same_Order()
{
// Arrange
var order = new Order { OrderId = 10, TotalAmount = 250.0m };
// Act
_repository.SaveOrder(order);
var savedOrder = _repository.GetOrder(10);
// Assert
Assert.IsNotNull(savedOrder);
Assert.AreEqual(order.OrderId, savedOrder.OrderId);
Assert.AreEqual(order.TotalAmount, savedOrder.TotalAmount);
}
}
- 실제 DB와 연결된 테스트
- 테스트 데이터 초기화 후 검증
- 운영 DB와 격리된 환경에서 안전하게 테스트 가능
🚧 5. 주의사항 및 최적화 팁
항목 | 설명 |
---|---|
테스트 속도 | 통합 테스트는 상대적으로 느림. 핵심 케이스만 테스트 |
테스트 격리 | 테스트마다 DB 초기화 필수 |
트랜잭션 처리 | TransactionScope 로 테스트 후 롤백 가능 |
CI/CD | 통합 테스트 전용 DB를 자동 구성하는 스크립트 필요 |
🎯 결과 요약
- 운영 DB와 분리된 테스트용 DB 설정 완료
- 테스트 데이터 초기화 및 클린업 전략 적용
- 실제 데이터 저장/조회 통합 테스트 구현
- 테스트 간 독립성 확보
🚀 다음 편 예고
👉 다음 글에서는 레거시 코드 리팩토링을 통해 DI(Dependency Injection)을 적용하고, 테스트 커버리지 도구 활용 및 테스트 코드 구조 정리 전략을 설명할 예정입니다.