푸터 콘텐츠로 바로가기
Iron Academy Logo
C# 배우기
C# 배우기

다른 카테고리

C# 단위 테스트에서 Fluent Assertions

[[academy-video-youtube({"vid": "TytferBCLOo", "start_time": "0", "title": "Fluent Assertions in Unit Testing in C#", "creator": "Tim Corey", "length": "10m 6s"})]]

단위 테스트는 한 번 작성되고 여러 번 읽히기 때문에 단언문은 테스트 Suite에서 가장 중요한 세부 사항 중 하나입니다. Assert.Equal(expected, actual)는 작동하지만, 단순히 두 문자열을 비교하는 실패 메시지는 항상 테스트가 무엇을 확인하고 있는지 설명하지 않습니다. Fluent Assertions는 검증 구문을 다시 정돈하여 라인이 그 규칙을 시행한다고 읽히게 하고, 실패 메시지는 차이보다는 문장이 됩니다.

동영상 "Fluent Assertions in Unit Testing in C#"에서 Tim Corey는 이미 SampleClass 예상 경로를 실행하는 xUnit 프로젝트를 가져온 후 간단한 공백 나누기 로직을 깨뜨리는 경계 사례(Eddie Van Halen)를 도입합니다. 그는 Fluent Assertions 패키지를 설치하고, .Should().Be() 체인을 사용하여 단언문을 재작성하고, 단일 값에 여러 조건을 체인하는 방법을 설명하며, 첫 번째 실패 조건에서 중단하는 대신 모든 실패 조건을 한 번에 보고하는 AssertionScope로 마무리합니다. 테스트 스위트가 간단한 동등성 차이가 명확히 읽히지 않을 정도로 성장한 팀은 여기서 업그레이드 경로를 찾을 수 있습니다.

xUnit 시작점

[0:35 - 2:46] 화면의 프로젝트는 작습니다: 생성자에서 전체 이름을 받아 공백에서 FirstNameLastName으로 분할하는 클래스 라이브러리와 예상 경로를 실행하는 xUnit 테스트 프로젝트가 있습니다. 테스트는 InlineData 속성이 있는 Theory를 사용하여 두 개의 입력("Tim Corey" 및 "Sue Storm")에 대해 동일한 로직을 실행하고, 각 단언문은 표준 Assert.Equal(expected, actual) 형식입니다.

[Theory]
[InlineData("Tim Corey", "Tim")]
[InlineData("Sue Storm", "Sue")]
public void TestFirstNameProperty(string fullName, string expected)
{
    var sample = new SampleClass(fullName);
    Assert.Equal(expected, sample.FirstName);
}
[Theory]
[InlineData("Tim Corey", "Tim")]
[InlineData("Sue Storm", "Sue")]
public void TestFirstNameProperty(string fullName, string expected)
{
    var sample = new SampleClass(fullName);
    Assert.Equal(expected, sample.FirstName);
}

스위트를 실행하면 여섯 개의 통과된 테스트가 나옵니다. 이것은 잘못된 것이 아니며, "첫 공백에서 마지막"에 맞는 경우에 대한 검증은 충분히 명확히 읽힙니다. 재미있는 부분은 이름이 그런 형태에 맞지 않을 때 시작됩니다.

행복 경로가 작동하지 않는 곳

[2:46 - 4:32] Tim이 선택한 에디지 케이스는 "Eddie Van Halen"입니다. 성이 "Van Halen"인 세 단어로 이루어진 이름이며, 첫 공백 이후의 토큰이 아닙니다. 현 구현은 스플릿된 인덱스 1을 잡아 성으로 하고, 클래스는 "Van"을 성으로 반환하고 "Halen"은 완전히 버립니다. 버그는 실제이며, 이를 해결하기 위한 첫 걸음은 실패하는 테스트를 작성하는 것입니다.

Assert.Equal("Van Halen", sample.LastName)로 테스트를 작성하는 것은 작동하지만, 실패 메시지는 문맥 없이 문자열 비교처럼 읽힙니다. Fluent Assertions로 전환하면 같은 테스트가 규칙을 단어로 표현하고, 실패 메시지는 테스트 중인 속성을 지칭합니다. 작은 스위트에서는 차이가 미미해 보입니다; 몇 백 개의 검증을 가진 스위트라면, 가독성 차이가 누적됩니다.

Fluent Assertions 설치

[4:32 - 5:10] 패키지는 FluentAssertions 아래 NuGet에 존재합니다. Tim은 레지스트리에서 가장 많이 다운로드 된 패키지 중 하나로 표시하여, 유용한 데이터로 설정합니다: 새로운 기여자는 통계적으로 이미 본 적이 있을 가능성이 높습니다. 테스트 프로젝트는 상단에 두 개의 using 지시문을 가져옵니다:

using FluentAssertions;
using FluentAssertions.Execution;
using FluentAssertions;
using FluentAssertions.Execution;

첫 번째는 대부분의 테스트가 필요로 하는 검증 확장 메서드를 가져옵니다. 두 번째는 비디오 후반에 다룰 AssertionScope 타입을 위해 존재합니다; 검증을 그룹화하지 않는 테스트는 그것을 제외할 수 있습니다.

Should.Be 구문

[5:10 - 6:14] Van Halen 테스트에 대한 최소한의 Fluent Assertions 재작성:

[Fact]
public void TestEdgeCaseNames()
{
    var sample = new SampleClass("Eddie Van Halen");
    sample.LastName.Should().Be("Van Halen");
}
[Fact]
public void TestEdgeCaseNames()
{
    var sample = new SampleClass("Eddie Van Halen");
    sample.LastName.Should().Be("Van Halen");
}

체인은 구어체 영어에 가깝게 읽힙니다. .Should() 확장은 비교를 설명하는 메서드를 가진 단언 객체를 반환합니다; .Be(...)는 동일성 체크를 수행합니다. 대문자 소문자 민감도, 앞뒤 공백 모두 해당 검사에 포함되며, 이는 대부분의 문자열 비교가 생산 코드에서 매치되는 행동과 같습니다.

망가진 구현에 대해 이 테스트를 실행하면 속성을 명시하고 차이를 설명하는 실패 메시지가 생성됩니다: "샘플.LastName이 'Van Halen'의 길이인 9로 예상되었으나 'Van'은 길이 3입니다." 이 메시지는 값을 생성한 표현식에 의해 검사 중인 값을 식별합니다, 이는 기본 Assert.Equal 형식이 제공하지 않는 종류의 문맥입니다.

복수 조건 체인

[6:14 - 8:10] 종종 단일 속성은 둘 이상의 규칙을 만족해야 합니다. Fluent Assertions는 .And로 조건을 구성하여 체인이 하나의 문장에 머물게 합니다:

sample.LastName.Should()
    .StartWith("Van")
    .And.EndWith("len")
    .And.Contain(" ");
sample.LastName.Should()
    .StartWith("Van")
    .And.EndWith("len")
    .And.Contain(" ");

체인의 각 링크는 별도의 조건입니다. 기본적으로 체인은 첫 번째 실패에서 보고를 멈춥니다: StartWith("Van")가 통과하고 EndWith("len")가 실패하면, 실패 메시지는 EndWith을 지정하고 단언이 거기서 멈춥니다. 각 조건이 독립적이고 모든 실패가 드러나길 원할 경우, 검증 범위(다음 섹션)가 그 행동을 변경합니다.

체인 구문은 열거형 동등성을 나열하기보다는 의도를 표현하는 쪽으로 기울입니다. "성은 Van으로 시작하고 len으로 끝나며 공백을 포함해야 한다"고 설명하는 테스트는 사양처럼 읽힙니다. 같은 로직을 세 개의 개별 Assert.True 호출로 작성하면 그들을 연결하는 이야기 없이 세 개의 부울 검사를 읽는 것과 같습니다.

모든 실패를 보고하는 검증 범위

[8:10 - 9:34] 첫 번째 실패에서 멈추는 기본 동작은 이전 조건에 의존하는 체인에는 적합합니다. 모든 조건이 독립적으로 중요한 체인에 대해서는, 단언을 AssertionScope로 감싸서 테스트에서 한 번의 실행에 모든 실패를 보고하게 만듭니다:

[Fact]
public void TestEdgeCaseNames()
{
    var sample = new SampleClass("Eddie Van Halen");

    using var _ = new AssertionScope();
    sample.LastName.Should().StartWith("Van");
    sample.LastName.Should().EndWith("len");
    sample.LastName.Should().Contain(" ");
}
[Fact]
public void TestEdgeCaseNames()
{
    var sample = new SampleClass("Eddie Van Halen");

    using var _ = new AssertionScope();
    sample.LastName.Should().StartWith("Van");
    sample.LastName.Should().EndWith("len");
    sample.LastName.Should().Contain(" ");
}

using-var-discard 라인은 표준 패턴입니다: 범위는 메서드 끝에서 변수가 범위를 벗어날 때까지 지속되며, 네임바는 자체적으로 읽히지 않음을 표시합니다. 테스트가 실패하면, 메시지는 각 실패 조건을 별도의 라인으로 포함하므로, 한 번의 실행으로 전체 이야기를 얻는 것입니다. 세 문제를 표면화하기 위해 세 번의 실행을 강요하는 대신에 말입니다. 여러 속성을 통해 반환된 객체의 형상을 검증하는 테스트의 경우, 이는 하나의 버그당 테스트 주기를 고치는 것과 한 번에 모두 고치는 것의 차이입니다.

마무리: 명세처럼 읽히는 테스트

[9:34 - 10:06] Fluent Assertions는 테스트가 무엇을 검증하는지 바꾸지 않습니다; 검증이 어떻게 읽히는지를 바꿉니다. .Should() 체인, .And 구성, 그리고 AssertionScope 그룹화는 모두 테스트 코드가 테스트 중인 동작의 서면 명세에 더 가까워지도록 합니다. 입력 유효성 검사를 위한 FluentValidation에서 사용된 동일한 유연한 스타일과 결합하여, 이 패턴은 새로운 사람들이 투어 없이도 읽을 수 있는 테스트 Suite와 유효성 검사 규칙을 생성합니다.

결론

[9:34 - 10:06] 테스트 프로젝트에 Fluent Assertions를 추가하려면 하나의 NuGet 설치, 두 개의 using 지시문, 단언문을 Assert.Equal(expected, actual)에서 actual.Should().Be(expected)로 재작성하는 작업이 필요합니다. 체인은 복수 조건 규칙을 하나의 문장으로 표현하고, AssertionScope는 첫 번째에서 멈추는 대신 단일 테스트가 모든 실패를 보고하게 만듭니다. 그 보상은 깨진 규칙을 설명하는 실패 메시지입니다, 이는 스택 트레이스와 문장 사이의 차이입니다.

예시 팁: 컬렉션을 단언할 때, 속성별 비교보다는 result.Should().BeEquivalentTo(expected)를 선호하십시오. 이것은 객체 그래프를 탐색하고 경로로 첫 번째 다른 속성을 보고합니다 (예를 들어, users[2].Address.City), 이는 50개의 요소가 있는 목록이 하나의 중첩된 필드에서 의견이 다를 때 "컬렉션이 같지 않음"이라는 메시지보다 훨씬 유용합니다.

전체 비디오를 그의 YouTube 채널에서 시청하고, 10분 교육 시리즈에서 읽기 쉽고 유지 관리 가능한 C# 테스트를 작성하는 것에 대한 더 많은 통찰력을 얻으세요.

Hero Worlddot related to C# 단위 테스트에서 Fluent Assertions
Hero Affiliate related to C# 단위 테스트에서 Fluent Assertions

사랑하는 것을 공유하여 더 많은 수익을 얻으세요

당신은 .NET, C#, Java, Python, 또는 Node.js를 다루는 개발자를 위한 콘텐츠를 만드나요? 당신의 전문성을 추가 수입으로 전환하세요!

아이언 서포트 팀

저희는 주 5일, 24시간 온라인으로 운영합니다.
채팅
이메일
전화해