C#에서 Moq를 사용하여 목에 전달된 개체를 확인하는 3가지 방법

17959 단어 csharpdotnettesting
단위 테스트를 작성할 때 Mocks를 사용하여 클래스 종속성 사용을 시뮬레이션할 수 있습니다.

일부 개발자는 모의 사용을 엄격하게 반대하지만 특히 모의 작업이 값을 반환하지 않지만 여전히 올바른 값으로 특정 메서드를 호출했는지 확인하려는 경우 유용할 수 있습니다.

이 기사에서는 C# 단위 테스트에서 Moq를 사용할 때 목에 전달된 값을 확인하는 3가지 방법을 배웁니다.

이 세 가지 방법을 더 잘 설명하기 위해 다음과 같은 방법을 만들었습니다.

public void UpdateUser(User user, Preference preference)
{
    var userDto = new UserDto
    {
        Id = user.id,
        UserName = user.username,
        LikesBeer = preference.likesBeer,
        LikesCoke = preference.likesCoke,
        LikesPizza = preference.likesPizza,
    };

    _userRepository.Update(userDto);
}

UpdateUser 는 단순히 userpreference 의 두 개체를 받아들여 하나의 UserDto 개체로 결합한 다음 클래스 생성자에 주입된 인터페이스인 Update_userRepository 메서드를 호출합니다.

보시다시피 _userRepository.Update 의 반환 값에는 관심이 없습니다. 오히려 올바른 값으로 호출하고 있는지 확인하는 데 관심이 있습니다.

우리는 3가지 방법으로 그것을 할 수 있습니다.

It.Is로 각 속성을 확인합니다.



가장 간단하고 가장 일반적인 방법은 It.Is<T> 메서드 내에서 Verify를 사용하는 것입니다.

[Test]
public void VerifyEachProperty()
{
    // Arrange
    var user = new User(1, "Davide");
    var preferences = new Preference(true, true, false);

    UserDto expected = new UserDto
    {
        Id = 1,
        UserName = "Davide",
        LikesBeer = true,
        LikesCoke = false,
        LikesPizza = true,
    };

    //Act

    userUpdater.UpdateUser(user, preferences);

    //Assert
    userRepo.Verify(_ => _.Update(It.Is<UserDto>(u =>
        u.Id == expected.Id
        && u.UserName == expected.UserName
        && u.LikesPizza == expected.LikesPizza
        && u.LikesBeer == expected.LikesBeer
        && u.LikesCoke == expected.LikesCoke
    )));
}


위의 예에서 It.Is<UserDto>Update 메서드에 전달된 정확한 항목을 확인하기 위해 userRepo 를 사용했습니다.

매개변수를 허용한다는 점에 유의하십시오. 해당 매개변수는 Func<UserDto, bool> 유형이며 이를 사용하여 기대치를 충족하는 시기를 정의할 수 있습니다.

이 특별한 경우에는 해당 함수 내의 모든 속성을 확인했습니다.

u =>
    u.Id == expected.Id
    && u.UserName == expected.UserName
    && u.LikesPizza == expected.LikesPizza
    && u.LikesBeer == expected.LikesBeer
    && u.LikesCoke == expected.LikesCoke


이 접근 방식은 몇 개의 필드에 대해서만 검사를 수행해야 할 때 잘 작동합니다. 그러나 더 많은 필드를 추가할수록 코드가 더 길고 복잡해집니다.

또한 이 접근 방식의 문제점은 실패할 경우 예상과 일치하지 않는 특정 필드에 대한 표시가 없기 때문에 실패의 원인이 무엇인지 이해하기 어려워진다는 것입니다.

다음은 오류 메시지의 예입니다.

Expected invocation on the mock at least once, but was never performed: _ => _.Update(It.Is<UserDto>(u => (((u.Id == 1 && u.UserName == "Davidde") && u.LikesPizza == True) && u.LikesBeer == True) && u.LikesCoke == False))

Performed invocations:

Mock<IUserRepository:1> (_):
    IUserRepository.Update(UserDto { UserName = Davide, Id = 1, LikesPizza = True, LikesCoke = False, LikesBeer = True })



오류를 발견할 수 있습니까? 그리고 5개가 아닌 15개의 필드를 확인한다면 어떨까요?

외부 기능으로 확인



또 다른 접근 방식은 함수를 외부화하는 것입니다.

[Test]
public void WithExternalFunction()
{
    //Arrange
    var user = new User(1, "Davide");
    var preferences = new Preference(true, true, false);

    UserDto expected = new UserDto
    {
        Id = 1,
        UserName = "Davide",
        LikesBeer = true,
        LikesCoke = false,
        LikesPizza = true,
    };

    //Act
    userUpdater.UpdateUser(user, preferences);

    //Assert
    userRepo.Verify(_ => _.Update(It.Is<UserDto>(u => AreEqual(u, expected))));
}

private bool AreEqual(UserDto u, UserDto expected)
{
    Assert.AreEqual(expected.UserName, u.UserName);
    Assert.AreEqual(expected.Id, u.Id);
    Assert.AreEqual(expected.LikesBeer, u.LikesBeer);
    Assert.AreEqual(expected.LikesCoke, u.LikesCoke);
    Assert.AreEqual(expected.LikesPizza, u.LikesPizza);

    return true;
}


여기서는 It.Is<T> 메서드에 외부 함수를 전달합니다.

이 접근 방식을 통해 보다 명시적이고 포괄적인 검사를 정의할 수 있습니다.

그것의 좋은 부분은 어설션을 더 잘 제어할 수 있고 테스트가 실패할 경우 더 나은 오류 메시지가 표시된다는 것입니다.

Expected string length 6 but was 7. Strings differ at index 5.
Expected: "Davide"
But was:  "Davidde"


나쁜 부분은 테스트 클래스를 다양한 메서드로 채우고 클래스를 쉽게 유지 관리하기 어렵게 만들 수 있다는 것입니다. 불행하게도 우리는 로컬 함수를 사용할 수 없습니다.

반면에 외부 기능을 사용하면 여러 테스트 사례에서 재사용할 수 있는 일부 테스트를 수행해야 할 때 외부 기능을 결합할 수 있습니다.

콜백으로 함수 매개변수 가로채기



마지막으로 Moq의 숨겨진 보석인 콜백을 사용할 수 있습니다.

콜백을 사용하면 메서드에서 호출한 항목에 대한 참조를 지역 변수에 저장할 수 있습니다.

[Test]
public void CompareWithCallback()
{
    // Arrange

    var user = new User(1, "Davide");
    var preferences = new Preference(true, true, false);

    UserDto actual = null;
    userRepo.Setup(_ => _.Update(It.IsAny<UserDto>()))
        .Callback(new InvocationAction(i => actual = (UserDto)i.Arguments[0]));

    UserDto expected = new UserDto
    {
        Id = 1,
        UserName = "Davide",
        LikesBeer = true,
        LikesCoke = false,
        LikesPizza = true,
    };

    //Act
    userUpdater.UpdateUser(user, preferences);

    //Assert
    userRepo.Verify(_ => _.Update(expected));
}


이러한 방식으로 로컬에서 사용하고 Verify 메서드에 의존하지 않고 해당 개체에 직접 어설션을 실행할 수 있습니다.

또는 레코드를 사용하는 경우 이전 예제에서 수행한 것처럼 자동 동등성 검사를 사용하여 Verify 방법을 단순화할 수 있습니다.

마무리



이 기사에서는 Moq로 조롱된 종속성에 전달된 객체에 대한 검사를 수행하는 3가지 방법을 살펴보았습니다.

각각의 방법에는 장단점이 있으며 가장 적합한 접근 방식을 선택하는 것은 귀하에게 달려 있습니다.

전달된 값에 대해 더 나은 검사를 수행할 수 있으므로 개인적으로 두 번째 및 세 번째 접근 방식을 선호합니다.

당신은 어때요?

지금은 즐거운 코딩하세요!

🐧

좋은 웹페이지 즐겨찾기