C# - Unit Test
준비
- 보통 프로그램이나 라이브러리 프로젝트에 추가 프로젝트로 유닛테스트 프레임워크를 사용하는 프로젝트를 만든다.
-
이렇게 하면 유닛테스트에 영향을 받지 않으면서 기존처럼 개발을 할 수 있다.
- 유닛테스트 프로젝트에 테스트 메소드를 만들면 테스트 탐색기에 리스트로 나온다.
유닛테스트 초기화와 실행 순서
지정한 유닛테스트만 실행
- 유닛테스트 리스트에서 특정 테스트만 실행하고 싶은 경우 그 항목만 선택 후 테스트를 실행한다
카테고리
- 유닛테스트를 비슷한 항목끼리 카테고리로 묶어서 카테고리 별로 테스트 할 수 있다.
유닛 테스트 예제
public void Test주고_받는_점수_계산하기()
{
PrivateType TestObject = new PrivateType(typeof(YakuLibNET.ScoreCalculator));
bool Is부모가_이긴 = true;
int 이긴_플레이어_점수 = 0;
object Result;
YakuLibNET.KUK_END_SCORE ResultScore;
// [테스트-부모가 이긴 경우]
Is부모가_이긴 = true;
이긴_플레이어_점수 = 3900;
Result = TestObject.InvokeStatic("주고_받는_점수_계산하기", Is부모가_이긴, 이긴_플레이어_점수);
ResultScore = (YakuLibNET.KUK_END_SCORE)Result;
Assert.AreEqual(3900, ResultScore.이긴_플레이어가_받는_점수);
Assert.AreEqual(1300, ResultScore.부모윈_진_자식이_내는_점수);
Is부모가_이긴 = true;
이긴_플레이어_점수 = 2000;
Result = TestObject.InvokeStatic("주고_받는_점수_계산하기", Is부모가_이긴, 이긴_플레이어_점수);
ResultScore = (YakuLibNET.KUK_END_SCORE)Result;
Assert.AreEqual(이긴_플레이어_점수, ResultScore.이긴_플레이어가_받는_점수);
Assert.AreEqual(700, ResultScore.부모윈_진_자식이_내는_점수);
Is부모가_이긴 = true;
이긴_플레이어_점수 = 7700;
Result = TestObject.InvokeStatic("주고_받는_점수_계산하기", Is부모가_이긴, 이긴_플레이어_점수);
ResultScore = (YakuLibNET.KUK_END_SCORE)Result;
Assert.AreEqual(이긴_플레이어_점수, ResultScore.이긴_플레이어가_받는_점수);
Assert.AreEqual(2600, ResultScore.부모윈_진_자식이_내는_점수);
Is부모가_이긴 = true;
이긴_플레이어_점수 = 11600;
Result = TestObject.InvokeStatic("주고_받는_점수_계산하기", Is부모가_이긴, 이긴_플레이어_점수);
ResultScore = (YakuLibNET.KUK_END_SCORE)Result;
Assert.AreEqual(이긴_플레이어_점수, ResultScore.이긴_플레이어가_받는_점수);
Assert.AreEqual(3900, ResultScore.부모윈_진_자식이_내는_점수);
Is부모가_이긴 = true;
이긴_플레이어_점수 = 10600;
Result = TestObject.InvokeStatic("주고_받는_점수_계산하기", Is부모가_이긴, 이긴_플레이어_점수);
ResultScore = (YakuLibNET.KUK_END_SCORE)Result;
Assert.AreEqual(이긴_플레이어_점수, ResultScore.이긴_플레이어가_받는_점수);
Assert.AreEqual(3600, ResultScore.부모윈_진_자식이_내는_점수);
// [자신이 이긴 경우]
Is부모가_이긴 = false;
이긴_플레이어_점수 = 1300;
Result = TestObject.InvokeStatic("주고_받는_점수_계산하기", Is부모가_이긴, 이긴_플레이어_점수);
ResultScore = (YakuLibNET.KUK_END_SCORE)Result;
Assert.AreEqual(이긴_플레이어_점수, ResultScore.이긴_플레이어가_받는_점수);
Assert.AreEqual(700, ResultScore.자식윈_진_부모가_내는_점수);
Assert.AreEqual(400, ResultScore.자식윈_진_자식이_내는_점수);
Is부모가_이긴 = false;
이긴_플레이어_점수 = 5200;
Result = TestObject.InvokeStatic("주고_받는_점수_계산하기", Is부모가_이긴, 이긴_플레이어_점수);
ResultScore = (YakuLibNET.KUK_END_SCORE)Result;
Assert.AreEqual(이긴_플레이어_점수, ResultScore.이긴_플레이어가_받는_점수);
Assert.AreEqual(2600, ResultScore.자식윈_진_부모가_내는_점수);
Assert.AreEqual(1300, ResultScore.자식윈_진_자식이_내는_점수);
Is부모가_이긴 = false;
이긴_플레이어_점수 = 7100;
Result = TestObject.InvokeStatic("주고_받는_점수_계산하기", Is부모가_이긴, 이긴_플레이어_점수);
ResultScore = (YakuLibNET.KUK_END_SCORE)Result;
Assert.AreEqual(이긴_플레이어_점수, ResultScore.이긴_플레이어가_받는_점수);
Assert.AreEqual(3600, ResultScore.자식윈_진_부모가_내는_점수);
Assert.AreEqual(1800, ResultScore.자식윈_진_자식이_내는_점수);
}
테스트 함수 리스트
public이 아닌 메소드 테스트 하기
- 유닛테스트를 할 때 가장 골치 아픈 것 중의 하나가 테스트할 함수가 public가 아닌 경우이다. 다행히 닷넷에서는 테스트 할 때만 public로 변경해 주는 기능이 있다(기존 코드 수정 없이).
- 정적(static) 메소드는 PrivateType 사용
- http://msdn.microsoft.com/ko-kr/library/microsoft.visualstudio.testtools.unittesting.privatetype.aspx
- 일반 메소드는 PrivateObject 사용
- http://msdn.microsoft.com/ko-kr/library/microsoft.visualstudio.testtools.unittesting.privateobject.aspx
public void Test주고_받는_점수_계산하기()
{
// ScoreCalculator는 private 멤버 함수이다.
PrivateType TestObject = new PrivateType(typeof(YakuLibNET.ScoreCalculator));
// [테스트-부모가 이긴 경우]
bool Is부모가_이긴 = true;
int 이긴_플레이어_점수 = 3900;
object Result = TestObject.InvokeStatic("주고_받는_점수_계산하기", Is부모가_이긴, 이긴_플레이어_점수);
YakuLibNET.KUK_END_SCORE ResultScore = (YakuLibNET.KUK_END_SCORE)Result;
Assert.AreEqual(3900, ResultScore.이긴_플레이어가_받는_점수);
Assert.AreEqual(1300, ResultScore.부모윈_진_자식이_내는_점수);
}
public class PrivateAccess
{
private static string NameStatic
{
get { return typeof(PrivateAccess).Name; }
}
private string Name { get; set; }
public PrivateAccess(string name)
{
this.Name = name;
}
private int Add(int lhs, int rhs)
{
return lhs + rhs;
}
private static int AddStatic(int lhs, int rhs)
{
return lhs + rhs;
}
private int Subtract(int lhs, int rhs)
{
return lhs - rhs;
}
}
[TestMethod()]
public void PrivateAccessConstructorTest()
{
// Arange
PrivateObject po = new PrivateObject(typeof(PrivateAccess), new object[] { "안녕" });
string name = "안녕";
// Act
// PrivateAccess target = new PrivateAccess(name);
// Assert
Assert.AreEqual(name, po.GetProperty("Name"));
}
public void AddTest()
{
PrivateObject po = new PrivateObject(typeof(PrivateAccess), new object[] { "안녕" });
//PrivateAccess_Accessor target = new PrivateAccess_Accessor(param0);
int lhs = 2;
int rhs = 3;
int expected = 5;
int actual;
actual = (int) po.Invoke("Add", new object[]{lhs, rhs});
Assert.AreEqual(expected, actual);
}
public void AddStaticTest()
{
PrivateType pt = new PrivateType(typeof(PrivateAccess));
int lhs = 1;
int rhs = 2;
int expected = 3;
int actual;
actual = (int) pt.InvokeStatic("AddStatic", new object[] { lhs, rhs });
Assert.AreEqual(expected, actual);
}
[TestMethod()]
[DeploymentItem("SampleLibrary.dll")]
public void NameTest()
{
PrivateObject po = new PrivateObject(new PrivateAccess("hello"));
// PrivateAccess_Accessor target = new PrivateAccess_Accessor(param0);
string expected = "hello";
string actual = po.GetProperty("Name") as string;
Assert.AreEqual(expected, actual);
}
[TestMethod()]
public void NameStaticTest()
{
PrivateType pt = new PrivateType(typeof(PrivateAccess));
string actual = pt.GetStaticProperty("NameStatic") as string;
Assert.AreEqual("PrivateAccess", actual);
}
public이 아닌 멤버나 프로퍼티 값 설정 하기
calss TEST
{
public int N1 { get; private set; }
}
var po = new PrivateObject(typeof(TEST));
po.SetFieldOrProperty("N1", 1);
var test = (TEST)po.Target;
static 클래스의 private 필드 설정하기
public class UniqueNumberRepository
{
const int MaxStockCount = 200000;
const int MinStockCount = 50000;
static Int64 seqNumber = 0;
static Int64 lastNumber = 0;
}
PrivateType TestObject = new PrivateType(typeof(UniqueNumberRepository));
TestObject.SetStaticField("seqNumber", 1);
TestObject.SetStaticField("lastNumber", 10);
static 클래스의 private 필드 참조하기
System.Transactions를 사용한 DB 유닛테스트
- 다중 커밋을 지원.
- 유닛테스트 코드에서 트랜잭션을 시작하여 제품 코드를 호출하고, 제품 코드측에서 데이터베이스에 변경을 한 후 커밋하여도 그 뒤의 유닛테스트 측에서 롤백하여 데이터베이스에 변경을 남기지 않는다.
예외 발생 여부 테스트
// InvalidOperationException 이 발생하면 테스트 성공
[TestMethod()]
[ExpectedException(typeof(InvalidOperationException))]
public void TestMethod()
{
// 여기서 InvalidOperationException 예외가 발생하는지 테스트
}
참고
이 글은 2019-03-04에 작성되었습니다.