[C#] NUnit에서 두 객체 간의 동등성을 비교하십시오.

한 객체가 다른 객체와 “동일”하다고 주장하려고합니다.

객체는 공용 속성이 많은 클래스의 인스턴스 일뿐입니다. 속성을 기반으로 NUnit이 평등을 주장하는 쉬운 방법이 있습니까?

이것이 현재의 해결책이지만 더 나은 것이 있다고 생각합니다.

Assert.AreEqual(LeftObject.Property1, RightObject.Property1)
Assert.AreEqual(LeftObject.Property2, RightObject.Property2)
Assert.AreEqual(LeftObject.Property3, RightObject.Property3)
...
Assert.AreEqual(LeftObject.PropertyN, RightObject.PropertyN)

내가 가고 싶은 것은 NUnit이 두 컬렉션의 내용이 동일한 지 확인하는 CollectionEquivalentConstraint와 같은 정신에 있습니다.



답변

재정의. 객체와 동일하고 단위 테스트에서 간단히 다음을 수행 할 수 있습니다.

Assert.AreEqual(LeftObject, RightObject);

물론 이것은 모든 개별 비교를 .Equals 메소드로 옮기는 것을 의미 할 수 있지만 여러 테스트에 해당 구현을 재사용 할 수 있으며 객체가 형제와 자신을 비교할 수 있어야하는 경우가 있습니다.


답변

어떤 이유로 든 Equals를 재정의 할 수없는 경우 리플렉션을 통해 공용 속성을 반복하여 각 속성을 지정하는 도우미 메서드를 작성할 수 있습니다. 이 같은:

public static class AssertEx
{
    public static void PropertyValuesAreEquals(object actual, object expected)
    {
        PropertyInfo[] properties = expected.GetType().GetProperties();
        foreach (PropertyInfo property in properties)
        {
            object expectedValue = property.GetValue(expected, null);
            object actualValue = property.GetValue(actual, null);

            if (actualValue is IList)
                AssertListsAreEquals(property, (IList)actualValue, (IList)expectedValue);
            else if (!Equals(expectedValue, actualValue))
                Assert.Fail("Property {0}.{1} does not match. Expected: {2} but was: {3}", property.DeclaringType.Name, property.Name, expectedValue, actualValue);
        }
    }

    private static void AssertListsAreEquals(PropertyInfo property, IList actualList, IList expectedList)
    {
        if (actualList.Count != expectedList.Count)
            Assert.Fail("Property {0}.{1} does not match. Expected IList containing {2} elements but was IList containing {3} elements", property.PropertyType.Name, property.Name, expectedList.Count, actualList.Count);

        for (int i = 0; i < actualList.Count; i++)
            if (!Equals(actualList[i], expectedList[i]))
                Assert.Fail("Property {0}.{1} does not match. Expected IList with element {1} equals to {2} but was IList with element {1} equals to {3}", property.PropertyType.Name, property.Name, expectedList[i], actualList[i]);
    }
}


답변

테스트 목적으로 만 Equals를 재정의하지 마십시오. 지루하고 도메인 로직에 영향을 미칩니다. 대신에

JSON을 사용하여 객체 데이터 비교

객체에 대한 추가 논리가 없습니다. 테스트를위한 추가 작업이 없습니다.

이 간단한 방법을 사용하십시오.

public static void AreEqualByJson(object expected, object actual)
{
    var serializer = new System.Web.Script.Serialization.JavaScriptSerializer();
    var expectedJson = serializer.Serialize(expected);
    var actualJson = serializer.Serialize(actual);
    Assert.AreEqual(expectedJson, actualJson);
}

잘 작동하는 것 같습니다. 테스트 실행기 결과 정보에 포함 된 JSON 문자열 비교 (객체 그래프)가 표시되어 무엇이 잘못되었는지 직접 확인할 수 있습니다.

또한 참고하십시오! 더 큰 복잡한 객체를 가지고 있고 그 일부를 비교하고 싶다면 위의 방법과 함께 사용할 익명 객체를 만들 수 있습니다 ( 시퀀스 데이터에 LINQ 사용 ).

public void SomeTest()
{
    var expect = new { PropA = 12, PropB = 14 };
    var sut = loc.Resolve<SomeSvc>();
    var bigObjectResult = sut.Execute(); // This will return a big object with loads of properties 
    AssExt.AreEqualByJson(expect, new { bigObjectResult.PropA, bigObjectResult.PropB });
}


답변

FluentAssertions 라이브러리를 사용해보십시오.

dto.ShouldHave(). AllProperties().EqualTo(customer);

http://www.fluentassertions.com/

NuGet을 사용하여 설치할 수도 있습니다.


답변

테스트를 활성화하기 위해 Equals를 재정의하지 않는 것이 좋습니다. Equals를 재정의하는 경우 GetHashCode도 재정의해야하거나 사전에 개체를 사용하는 경우 예기치 않은 결과가 발생할 수 있습니다.

나는 미래에 속성을 추가하기 위해 위의 반사 접근 방식을 좋아합니다.

그러나 빠르고 간단한 솔루션의 경우 객체가 동일한 지 테스트하는 도우미 메서드를 만들거나 테스트 전용으로 유지하는 클래스에서 IEqualityComparer를 구현하는 것이 가장 쉬운 경우가 많습니다. IEqualityComparer 솔루션을 사용할 때는 GetHashCode 구현에 신경 쓸 필요가 없습니다. 예를 들면 다음과 같습니다.

// Sample class.  This would be in your main assembly.
class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}

// Unit tests
[TestFixture]
public class PersonTests
{
    private class PersonComparer : IEqualityComparer<Person>
    {
        public bool Equals(Person x, Person y)
        {
            if (x == null && y == null)
            {
                return true;
            }

            if (x == null || y == null)
            {
                return false;
            }

            return (x.Name == y.Name) && (x.Age == y.Age);
        }

        public int GetHashCode(Person obj)
        {
            throw new NotImplementedException();
        }
    }

    [Test]
    public void Test_PersonComparer()
    {
        Person p1 = new Person { Name = "Tom", Age = 20 }; // Control data

        Person p2 = new Person { Name = "Tom", Age = 20 }; // Same as control
        Person p3 = new Person { Name = "Tom", Age = 30 }; // Different age
        Person p4 = new Person { Name = "Bob", Age = 20 }; // Different name.

        Assert.IsTrue(new PersonComparer().Equals(p1, p2), "People have same values");
        Assert.IsFalse(new PersonComparer().Equals(p1, p3), "People have different ages.");
        Assert.IsFalse(new PersonComparer().Equals(p1, p4), "People have different names.");
    }
}


답변

여기에 언급 된 여러 가지 접근법을 시도했습니다. 대부분 객체를 직렬화하고 문자열 비교를 수행합니다. 매우 쉽고 일반적으로 매우 효과적이지만, 실패했을 때 약간 짧아지고 다음과 같은 것이보고됩니다.

Expected string length 2326 but was 2342. Strings differ at index 1729.

차이점이 어디에 있는지 파악하는 것이 가장 어렵다.

FluentAssertions의 객체 그래프 비교 (예 :)를 사용하면 다음과 같은 결과a.ShouldBeEquivalentTo(b) 를 얻을 수 있습니다.

Expected property Name to be "Foo" but found "Bar"

훨씬 좋습니다. FluentAssertions를 지금 받으십시오. 나중에 기뻐할 것입니다 (이를 찬성한다면 FluentAssertions가 처음 제안 된 곳 의 dkl의 답변찬성 하십시오 ).


답변

ChrisYoxall에 동의합니다. 테스트 목적으로 순수하게 주 코드에서 Equals를 구현하는 것은 좋지 않습니다.

일부 응용 프로그램 논리에 필요하기 때문에 Equals를 구현하는 경우 괜찮습니다. 그러나 테스트 전용 코드를 깔끔하게 유지하십시오 (또한 테스트를 위해 동일한 검사의 의미가 앱에 필요한 것과 다를 수 있음).

간단히 말해, 테스트 전용 코드는 수업에서 제외하십시오.

대부분의 클래스에는 리플렉션을 사용하여 속성을 간단하게 비교하면 충분하지만 개체에 복잡한 속성이있는 경우 재귀가 필요할 수 있습니다. 다음 참조를 할 경우 순환 참조 또는 이와 유사한 것을주의하십시오.

교활한