이번 글에서는 xUnit에 대해 알아보겠습니다. 개발을 하다 보면 개발한 클래스나 함수에 대해 테스트가 필요할 때가 있습니다. 수동으로 값을 대입하거나, 화면상으로 눌러가며 테스트도 하지만 코드가 방대해지면 자동화된 단위 테스트는 필수가 됩니다.
C#에서는 여러 가지 프레임워크가 있지만 이 글에서는 그 중 xUnit에 대해 알아보겠습니다.
프로젝트 생성
간단하게 콘솔 앱을 하나 생성해줍니다. 그리고 아래와 같이 xUnit 테스트 프로젝트도 생성해줍니다.
만들어줄 프로젝트 이름은 테스트 할 프로젝트 이름.Tests로 만들어 줍니다. 콘솔 앱에서는 아래와 같이 간단하게 코드를 만들어줍니다.
namespace ConsoleApp1
{
public class MathClass
{
public MathClass()
{
}
static public int Sum(int a, int b)
{
int sum = a + b;
return sum;
}
static public int Division(int a, int b)
{
int div = a / b;
return div;
}
}
internal class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello, World!");
for (int i = 1; i < 10; i++)
{
for (int j = 1; j < 10; j++)
{
Console.WriteLine($"Division: {i}/{j} = {MathClass.Division(i, j)}");
}
}
}
}
}
만들어 준 xUnit 프로젝트에서 이 콘솔 앱 프로젝트를 참조할 수 있게 추가해줍니다. 종속성을 우 클릭 한 뒤 프로젝트 참조 추가를 눌러줍니다.
그리고 콘솔 앱 프로젝트를 선택하여 추가해줍니다.
테스트 기본 단위 : [Fact]
[Fact] 어트리뷰트는 xUnit의 테스트 러너에게 메서드가 실행 가능한 독립된 테스트 케이스임을 알려주는 속성입니다.
public class UnitTest1
{
[Fact]
public void BasicSumTest()
{
int a = 7;
int b = 8;
int result = MathClass.Sum(a, b) + 1; // 틀린 결과
Assert.Equal(15, result);
}
}
Assert.Equal을 사용하여 값이 같은지 테스트 합니다. 계산이 같으면 이상이 없지만 만약 Sum 함수에서 틀린 값을 반환하면 에러가 발생합니다. 일부로 Sum 함수 + 1을 더해서 테스트를 진행하면 아래와 같이 표시합니다.
보시면 기대된(Expected) 값은 15인데 실제로는(Actual) 16이여서 에러를 발생 시켰습니다. 참고로 테스트 탐색기는 테스트 메뉴에서 테스트 탐색기를 클릭하면 표시됩니다.
Assert.Equal 외에도 다른 메서드도 있습니다.
| 메서드 | 용도 |
|---|---|
| Assert.True / False | 결과가 참/거짓인지 확인 |
| Assert.Null / NotNull | 객체가 생성되었는지(null인지) 확인 |
| Assert.Empty / NotEmpty | 컬렉션(List 등)이나 문자열이 비었는지 확인 |
| Assert.Contains | 문자열이나 리스트에 특정 값이 포함되었는지 확인 |
| Assert.Throws | 특정 상황에서 **예외(Exception)**가 발생하는지 확인 |
| Assert.Same | 두 객체가 완전히 동일한 인스턴스(참조)인지 확인 |
| Assert.IsType | 결과값이 특정 클래스 타입인지 확인 |
다양한 테스트 데이터를 주입 : [Theory]
위 사례는 값을 하나만 넣어 테스트 가능합니다. [Theory] 속성을 사용하면 여러 개의 테스트 데이터를 만들어 입력할 수 있습니다. 그 첫 번째 방식이 [InlineData] 속성과 같이 사용하는 것입니다. 아래와 같이 Theory와 InlineData를 사용하여 테스트를 실행해 봅니다.
[Theory]
[InlineData(10, 5, 2)]
[InlineData(20, 4, 5)]
[InlineData(100, 10, 10)]
[InlineData(0, 0, 0)]
public void DivisionTest(int a, int b, int expected)
{
Assert.Equal(expected, MathClass.Division(a, b));
}
DivisionTest를 실행하면 테스트 탐색기에서 아래와 같이 표시합니다.
DivisionTest 아래에 4개의 테스트 결과가 나타나며 각각의 항목으로 표시됩니다. 그중 0으로 나눈 테스트의 경우 System.DivideByZeroException이 발생했다는 것을 볼 수 있습니다.
두 번째 방식은 MemberData 속성을 사용하는 것입니다. 첫 InlineData는 개발자가 또 일일이 만드는 방식이라면 MemberData는 데이터를 만들어줘서 테스트 케이스에 입력하는 방식입니다. 대신 MemeberData는 static IEnumerable<object[]> 형태여야 합니다.
public static IEnumerable<object[]> DivData()
{
List<object[]> ret = new List<object[]>();
Random rand = new Random();
for (int i = 0; i < 1000; i++)
{
int input1 = rand.Next(int.MinValue, int.MaxValue) % 100;
int input2 = rand.Next(int.MinValue, int.MaxValue) % 100;
object[] obj = new object[] { input1, input2 };
ret.Add(obj);
}
return ret;
}
[Theory]
[MemberData(nameof(DivData))]
public void DivTest3(int input1, int input2)
{
int div = MathClass.Division(input1, input2);
}
무작위로 1000개의 데이터를 만들어 준 뒤 MemberData 속성에 넣어주면 됩니다. 위 코드를 테스트 탐색기에서 실행하면 아래와 같이 표현됩니다.
많은 데이터로 테스트를 진행해야 할 땐 위 MemberData 방식을 사용하면 편리합니다.
MemberData 방식 말고 ClassData 속성을 사용하는 방법도 있습니다. 비슷하지만 아래 차이가 있습니다.
| 구분 | MemberData | ClassData |
| 위치 | 테스트 클래스 내부 (static) | 별도의 외부 클래스 |
| 관리 방식 | 메서드나 프로퍼티 형태 | 독립된 클래스 형태 |
| 재사용성 | 해당 파일 안에서 주로 사용 | 여러 테스트 파일에서 공유 가능 |
| 구현 난이도 | 매우 쉬움 (static 함수만 만들면 끝) | 보통 (인터페이스 상속 필요) |
| 주요 용도 | 특정 테스트 전용 복잡한 데이터 | 전사적으로 쓰이는 공통 테스트 데이터 |
이 글에서는 다루진 않겠습니다.
WPF 코드 테스트하기
WPF 프로그램을 MVVM으로 개발하면 ViewModel도 xUnit으로 테스트 하기 편리합니다. 기존과 같이 종속성에서 프로젝트 참조를 하면 되는데 다만 xUnit 프로젝트에 PropertyGroup에 아래와 같이 2개를 입력해야 합니다.
<PropertyGroup>
<TargetFramework>net8.0-windows</TargetFramework>
<UseWPF>true</UseWPF>
</PropertyGroup>
나머지 부분은 위와 비슷하니 WPF도 똑같이 테스트 해보시면 됩니다.
이상으로 C#에서 xUnit 사용법에 대해 알아보았습니다.






