프로그래밍

C# Mock을 이용한 테스트 환경 조성

친절한티스 2014. 5. 18. 13:50

아래와 같이 DB에서 학생의 성적들을 얻어와 점수의 평균을 구하여 출력하는 로직이 있습니다.

// 학생 성적 모델
public class StudentResult
{
	int MathResult;
	int EnglishResult;
	int ScienceResult;
}

// 학생 성적 DB 컨텍스트
public class StudentDBContext : DbContext
{
	public DbSet<StudentResult> StudentResultTable { get; set; }
}

// DB에서 학생들의 성적을 얻어와 평균을 구합니다.
public class School
{
	public float GetStudentAvr()
	{
		var cont = new StudentDBContext();
		int studentCount = cont.StudentResultTable.Count();
		int resultSum = 0;
		foreach( var student in cont.StudentResultTable )
		{
			resultSum += student.MathResult;
			resultSum += student.EnglishResult;
			resultSum += student.ScienceResult;
		}
		float resultAvr = resultSum / studentCount;
		return resultAvr;
	}
}

이제 이 로직을 테스트 해보겠습니다.

[TestClass]
public class SchoolTest
{
	[TestMethod]
	public void GetStudentAvrTest()
	{
		var schoolTest = new School();
		float reulstAvr = schooolTest.GetStudentAvr();
		Assert.AreEqual(reulstAvr, "기대값");
	}
}

간단한 테스트 로직입니다. 하지만 예상한대로 기대값을 주어도 위 테스트는 실패하게 됩니다. 왜냐하면 테스트 환경에서는 실제 DB가 갖춰져 있지 않기 때문이죠. 실제 DB에 접속을 할수 없으니 School 클래스에서 사용하고 있는 StudenDBContext 객체에 예외 상황이 발생하게 됩니다.


그렇다면 위와 같이 DB를 사용하는 로직은 테스트를 못하는 것인가? 아닙니다. 바로 Mock을 이용하면 됩니다. Mock이란 단어 그대로 실제 그것인 것 마냥 흉내를 내주는 장치입니다. 이를 이용하면 실제 DB에 접속해서 데이터를 가져오는 것처럼 흉내를 낼수 있습니다.


먼저 Mock을 사용하기 위해서는 해당 라이브러리를 설치하여야 합니다. 저는 많이 애용되고 있다는 Moq4 라이브러리를 설치하였습니다.




도구 - 라이브러리 패키지 관리자 - 솔루션용 NuGet 패키지 관리를 통해 Moq 을 설치합니다.


이제 Mock을 이용해 진짜 DB를 사용하는 듯하게 해줄 모형을 만들어보겠습니다. 먼저 아래와 같이 인터페이스를 하나 만듭니다. 기존에는 직접 DBContext를 다뤘지만, 앞으로 만들 Mock을 사용하기 위해서는 약간의 추상화가 필요합니다.

public interface IStudentDBContextController
{
	int GetStudentsCount();
	IQueryable<StudentResult> GetStudentResultsAll();
}

다음, 실제 로직에서 직접 DBContext를 사용하던 부분을 인터페이스를 이용 하도록 아래와 같이 수정하여 줍니다.

// 인터페이스 구현
public class StudentDBContextController : IStudentDBContextController
{
	private StudentDBContext cont = new StudentDBContext();
	public int GetStudentsCount()
	{
		return cont.StudentResultTable.Count();
	}
	public IQueryable<studentresult> GetStudentResultsAll()
	{
		return cont.StudentResultTable;
	}
}

// 인터페이스를 사용한 변경된 School 클래스
public class School
{
	IStudentDBContextController StudentDBContextCont { get; set; }
	
	public School() : this(new StudentDBContextController())
	{
	}

	public School(IStudentDBContextController newStudentDBContextController)
	{
		StudentDBContextCont = newStudentDBContextController;
	}
	
	public float GetStudentAvr()
	{
		var cont = new StudentDBContext();
		int studentCount = StudentDBContextCont.GetStudentsCount();
		var studentResultTable = StudentDBContextCont.GetStudentResultsAll();
		int resultSum = 0;
		foreach( var student in studentResultTable )
		{
			resultSum += student.MathResult;
			resultSum += student.EnglishResult;
			resultSum += student.ScienceResult;
		}
		float resultAvr = resultSum / studentCount;
		return resultAvr;
	}
}

실제 코드 부분은 이걸로 됐습니다. 다음 테스트 코드를 보겠습니다. 위에서 만드는 IStudentDBContextController의 Mock을 만들어 인터페이스를 함수를 호출했을때 실제 DB에서 값을 얻어오는 것처럼 셋팅을 해줍니다.


[TestClass]
public class SchoolTest
{
	private Mock<IStudentDBContextController> mock = new Mock<IStudentDBContextController>();
	
	[TestInitialize]
	public void Initialize()
	{
		// Mock 함수 셋업
		mock.Setup(cont => cont.GetStudentsCount().Returns(4);
		mock.Setup(cont => cont.GetStudentResultsAll().Returns(
			var studentResultsAll new List<StudentResult> {
				new StudentResult { MathResult = 90,
                            EnglishResult = 70,
							ScienceResult = 60 },
				new StudentResult { MathResult = 80,
                            EnglishResult = 90,
							ScienceResult = 70 },
				new StudentResult { MathResult = 60,
                            EnglishResult = 70,
							ScienceResult = 70 },
				new StudentResult { MathResult = 80,
                            EnglishResult = 50,
							ScienceResult = 90 }
			};
			return studentResultsAll.AsQueryable();
		);
	}
	
	[TestMethod]
	public void GetStudentAvrTest()
	{
		// Mock 객체로 사용하도록 수정
		var schoolTest = new School(mock.Mock.Object);
		float reulstAvr = schooolTest.GetStudentAvr();
		Assert.AreEqual(reulstAvr, "기대값");
	}
}

아래 GetStudentAvrTest를 살펴 보시면 School 객체 생성자 초기값을 Mock 객체로 넘겨주고 있습니다. 이를 통해 GetStudentAvr 로직에서 Mock에 셋팅된 함수 값을 리턴받게 되고, 실제 DB에 접속된 듯한 환경을 조성할수 있게됩니다. 이로서 해당 로직이 제대로 값을 계산하는지 테스트 할수 있게 되었습니다.


참조 : https://github.com/Moq/moq4/wiki/Quickstart

반응형