เปรียบเทียบความเท่าเทียมกันระหว่างสองวัตถุใน NUnit


127

ฉันพยายามยืนยันว่าวัตถุหนึ่ง "เท่ากับ" กับวัตถุอื่น

อ็อบเจ็กต์เป็นเพียงอินสแตนซ์ของคลาสที่มีคุณสมบัติสาธารณะมากมาย มีวิธีง่ายๆในการให้ 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)

สิ่งที่ฉันต้องการจะเป็นไปในจิตวิญญาณเดียวกันกับ CollectionEquivalentConstraint โดยที่ NUnit ตรวจสอบว่าเนื้อหาของสองคอลเลคชันเหมือนกัน

คำตอบ:


51

Override .Equals สำหรับวัตถุของคุณและในการทดสอบหน่วยคุณสามารถทำได้:

Assert.AreEqual(LeftObject, RightObject);

แน่นอนว่านี่อาจหมายความว่าคุณเพียงแค่ย้ายการเปรียบเทียบแต่ละรายการไปยังเมธอด. Equals แต่จะช่วยให้คุณสามารถนำการใช้งานนั้นมาใช้ซ้ำสำหรับการทดสอบหลาย ๆ ครั้งและอาจเป็นเรื่องที่สมเหตุสมผลหากวัตถุควรสามารถเปรียบเทียบตัวเองกับพี่น้องได้


2
ขอบคุณ lassevk สิ่งนี้ได้ผลสำหรับฉัน! ฉันใช้. Equals ตามแนวทางที่นี่: msdn.microsoft.com/en-us/library/336aedhh(VS.80).aspx
Michael Haren

12
และ GetHashCode () ชัด ;-p
Marc Gravell

หมายเลข 1 ในรายการในหน้านั้นคือการแทนที่ GetHashCode และเขาบอกว่าเขาปฏิบัติตามหลักเกณฑ์เหล่านั้น :) แต่ใช่ข้อผิดพลาดทั่วไปที่จะเพิกเฉยต่อสิ่งนั้น โดยทั่วไปแล้วคุณจะสังเกตเห็นได้ตลอดเวลาไม่ใช่เรื่องผิด แต่เมื่อคุณทำมันก็เหมือนกับช่วงเวลาหนึ่งที่คุณพูดว่า "โอ้เฮ้ทำไมงูตัวนี้ถึงกางเกงของฉันและทำไมเขาถึงกัดตูดฉัน"
Lasse V.Karlsen

1
ข้อแม้ที่สำคัญประการหนึ่ง: หากวัตถุของคุณใช้งานIEnumerableด้วยจะถูกเปรียบเทียบเป็นคอลเล็กชันโดยไม่คำนึงถึงการใช้งานที่ลบล้างEqualsเนื่องจาก NUnit ให้IEnumerableความสำคัญมากกว่า ดูNUnitEqualityComparer.AreEqualรายละเอียดวิธีการ คุณสามารถแทนที่ตัวเปรียบเทียบได้โดยใช้หนึ่งในUsing()วิธีการของข้อ จำกัด ด้านความเท่าเทียมกัน ถึงกระนั้นก็ไม่เพียงพอที่จะใช้งานที่ไม่ใช่แบบทั่วไปIEqualityComparerเนื่องจากอะแด็ปเตอร์ NUnit ใช้
Kaleb Pederson

13
ข้อแม้เพิ่มเติม: การใช้งานGetHashCode()กับประเภทที่เปลี่ยนแปลงได้จะทำงานผิดปกติหากคุณเคยใช้วัตถุนั้นเป็นกุญแจสำคัญ IMHO เอาชนะEquals(), GetHashCode()และทำให้ไม่เปลี่ยนรูปวัตถุเพียงสำหรับการทดสอบไม่ได้ทำให้รู้สึก
bavaza

118

หากคุณไม่สามารถลบล้าง 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]);
    }
}

@wesley: ไม่เป็นความจริง Type.GetProperties Method: ส่งคืนคุณสมบัติสาธารณะทั้งหมดของ Type ปัจจุบัน ดูmsdn.microsoft.com/en-us/library/aky14axb.aspx
Sergii Volchkov

4
ขอบคุณ อย่างไรก็ตามฉันต้องเปลี่ยนลำดับของพารามิเตอร์ที่แท้จริงและที่คาดไว้เนื่องจากการแปลงเป็นสิ่งที่คาดว่าจะเป็นพารามิเตอร์ก่อนที่จะเกิดขึ้นจริง
Valamas

นี่เป็นแนวทางที่ดีกว่า IMHO การลบล้าง Equal & HashCode ไม่ควรต้องอิงจากการเปรียบเทียบทุกฟิลด์และบวกที่น่าเบื่อมากที่จะทำในทุกวัตถุ เก่งมาก!
Scott White

3
วิธีนี้ใช้ได้ผลดีหากประเภทของคุณมีเพียงประเภทพื้นฐานเป็นคุณสมบัติ อย่างไรก็ตามหากประเภทของคุณมีคุณสมบัติที่มีประเภทที่กำหนดเอง (ที่ไม่ใช้ Equals) จะล้มเหลว
Bobby Cannon

เพิ่มการเรียกซ้ำสำหรับคุณสมบัติของวัตถุ แต่ฉันต้องข้ามคุณสมบัติที่จัดทำดัชนีไว้:
cerhart

113

อย่าลบล้าง 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 });
}

1
นี่เป็นวิธีที่ยอดเยี่ยมในการทดสอบโดยเฉพาะอย่างยิ่งหากคุณกำลังจัดการกับ JSON อยู่เสมอ (เช่นใช้ไคลเอนต์ที่พิมพ์เพื่อเข้าถึงบริการเว็บ) คำตอบนี้น่าจะสูงกว่านี้มาก
Roopesh Shenoy

1
ใช้ Linq! @DmitryBLR (ดูย่อหน้าสุดท้ายในคำตอบ) :)
สูงสุด

3
นี่เป็นความคิดที่ดี ฉันจะใช้ Json.NET ที่ใหม่กว่า: var expectedJson = Newtonsoft.Json.JsonConvert.SerializeObject (คาดว่า);
BrokeMyLegBiking

2
สิ่งนี้ใช้ไม่ได้กับการอ้างอิงแบบวงกลม ใช้github.com/kbilsted/StatePrinterแทนเพื่อประสบการณ์ที่ดีขึ้นในแนวทาง JSON
Carlo V. Dango

2
นั่นเป็นความจริง @KokaChernov และบางครั้งคุณต้องการทดสอบล้มเหลวหากการสั่งซื้อไม่เหมือนกัน แต่ถ้าคุณไม่ต้องการล้มเหลวหากการสั่งซื้อไม่เหมือนกันคุณสามารถจัดเรียงอย่างชัดเจน (โดยใช้ linq) ในรายการก่อนที่จะส่งไปยังวิธี AreEqualByJson รูปแบบง่ายๆของการ "จัดเรียงใหม่" วัตถุของคุณก่อนการทดสอบอยู่ในตัวอย่างโค้ดสุดท้ายในคำตอบ ฉันคิดว่ามันเป็น "สากล" มาก! :)
สูงสุด

91

ลองใช้ไลบรารี FluentAssertions:

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

http://www.fluentassertions.com/

นอกจากนี้ยังสามารถติดตั้งโดยใช้ NuGet


18
ShouldHave เลิกใช้แล้วดังนั้นควรเป็น dto.ShouldBeEquivalentTo (ลูกค้า); แทน
WhiteKnight

2
นี่คือคำตอบที่ดีที่สุดสำหรับเหตุผลนี้
Todd Menier

ShouldBeEquivalent เป็นรถบั๊กกี้ :(
Konstantin Chernov

3
เพิ่งมีปัญหาเดียวกันและใช้สิ่งต่อไปนี้ซึ่งดูเหมือนว่าจะใช้งานได้ดี:actual.ShouldBeEquivalentTo(expected, x => x.ExcludingMissingMembers())
stt106

1
นี่คือ lib ที่ยอดเยี่ยม! ไม่จำเป็นต้องลบล้าง Equals และ (หากเท่ากับจะถูกลบล้างต่อไปเช่นสำหรับอ็อบเจ็กต์ค่า) ไม่ต้องอาศัยการใช้งานที่ถูกต้อง นอกจากนี้ยังมีการพิมพ์ความแตกต่างอย่างสวยงามเช่น Hamcrest ทำกับ Java
kap

35

ฉันไม่ต้องการแทนที่ 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.");
    }
}

ค่าเท่ากับไม่จัดการกับค่าว่าง ฉันจะเพิ่มสิ่งต่อไปนี้ก่อนคำสั่งส่งคืนของคุณในวิธีการเท่ากับ ถ้า (x == null && y == null) {กลับจริง; } if (x == null || y == null) {return false; } ฉันแก้ไขคำถามเพื่อเพิ่มการสนับสนุนค่าว่าง
Bobby Cannon

ไม่ทำงานสำหรับฉันด้วยการโยน NotImplementedException ใหม่ (); ใน GetHashCode เหตุใดฉันจึงต้องใช้ฟังก์ชันนั้นใน IEqualityComparer ไม่ว่าจะด้วยวิธีใด
love2code

15

ฉันได้ลองใช้หลายวิธีที่กล่าวถึงที่นี่ ส่วนใหญ่เกี่ยวข้องกับการจัดลำดับวัตถุของคุณและทำการเปรียบเทียบสตริง แม้ว่าจะง่ายสุด ๆ และโดยทั่วไปแล้วมีประสิทธิภาพมาก แต่ฉันพบว่ามันสั้นไปหน่อยเมื่อคุณล้มเหลวและมีการรายงานสิ่งนี้:

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ตอนนี้คุณจะดีใจในภายหลัง (และหากคุณโหวตให้คะแนนสิ่งนี้โปรดเพิ่มคะแนนคำตอบของ dkl ซึ่งแนะนำ FluentAssertions เป็นครั้งแรก)


9

ฉันเห็นด้วยกับ ChrisYoxall - การใช้ Equals ในโค้ดหลักของคุณเพื่อวัตถุประสงค์ในการทดสอบนั้นไม่ดี

หากคุณกำลังใช้ Equals เนื่องจากตรรกะของแอปพลิเคชันบางอย่างจำเป็นต้องใช้ก็ไม่เป็นไร แต่อย่าให้โค้ดสำหรับการทดสอบเพียงอย่างเดียวไม่เกะกะ (นอกจากนี้ความหมายของการตรวจสอบสิ่งเดียวกันสำหรับการทดสอบอาจแตกต่างจากที่แอปของคุณต้องการ)

กล่าวโดยย่อคืออย่าให้โค้ดสำหรับการทดสอบเท่านั้นออกจากชั้นเรียนของคุณ

การเปรียบเทียบคุณสมบัติตื้น ๆ โดยใช้การสะท้อนควรเพียงพอสำหรับคลาสส่วนใหญ่แม้ว่าคุณอาจต้องเรียกคืนหากวัตถุของคุณมีคุณสมบัติที่ซับซ้อน หากติดตามการอ้างอิงโปรดระวังการอ้างอิงแบบวงกลมหรือสิ่งที่คล้ายกัน

เขี้ยวลากดิน


จับใจความได้ดีในการอ้างอิงแบบวงกลม ง่ายต่อการเอาชนะหากคุณเก็บพจนานุกรมของวัตถุไว้ในแผนภูมิเปรียบเทียบ
Lucas B

6

ข้อ จำกัด คุณสมบัติที่เพิ่มใน NUnit 2.4.2 ทำให้โซลูชันที่อ่านได้ง่ายกว่าแบบดั้งเดิมของ OP และสร้างข้อความแสดงความล้มเหลวที่ดีกว่ามาก ไม่ใช่วิธีการทั่วไป แต่ถ้าคุณไม่จำเป็นต้องทำในชั้นเรียนมากเกินไปก็เป็นวิธีแก้ปัญหาที่เพียงพอ

Assert.That(ActualObject, Has.Property("Prop1").EqualTo(ExpectedObject.Prop1)
                          & Has.Property("Prop2").EqualTo(ExpectedObject.Prop2)
                          & Has.Property("Prop3").EqualTo(ExpectedObject.Prop3)
                          // ...

ไม่ใช่วัตถุประสงค์ทั่วไปเหมือนกับการใช้งานEqualsแต่จะให้ข้อความแสดงความล้มเหลวที่ดีกว่า

Assert.AreEqual(ExpectedObject, ActualObject);

4

โซลูชัน JSON ของ Max Wikstrom (ด้านบน) เหมาะสมกับฉันมากที่สุดมันสั้นสะอาดและที่สำคัญที่สุดคือใช้งานได้ โดยส่วนตัวแม้ว่าฉันต้องการใช้การแปลง JSON เป็นวิธีการแยกต่างหากและวางคำยืนยันกลับเข้าไปในการทดสอบหน่วยเช่นนี้ ...

วิธีการช่วยเหลือ:

public string GetObjectAsJson(object obj)
    {
        System.Web.Script.Serialization.JavaScriptSerializer oSerializer = new System.Web.Script.Serialization.JavaScriptSerializer();
        return oSerializer.Serialize(obj);
    }

การทดสอบหน่วย:

public void GetDimensionsFromImageTest()
        {
            Image Image = new Bitmap(10, 10);
            ImageHelpers_Accessor.ImageDimensions expected = new ImageHelpers_Accessor.ImageDimensions(10,10);

            ImageHelpers_Accessor.ImageDimensions actual;
            actual = ImageHelpers_Accessor.GetDimensionsFromImage(Image);

            /*USING IT HERE >>>*/
            Assert.AreEqual(GetObjectAsJson(expected), GetObjectAsJson(actual));
        }

FYI - คุณอาจต้องเพิ่มการอ้างอิงไปยัง System.Web.Extensions ในโซลูชันของคุณ


4

นี่เป็นกระทู้เก่า แต่ฉันสงสัยว่ามีเหตุผลที่ทำไมไม่เสนอคำตอบNUnit.Framework.Is.EqualToและNUnit.Framework.Is.NotEqualTo?

เช่น:

Assert.That(LeftObject, Is.EqualTo(RightObject)); 

และ

Assert.That(LeftObject, Is.Not.EqualTo(RightObject)); 

4
เพราะไม่ได้พิมพ์รายละเอียดว่าแตกต่างอะไร
Shrage Smilowitz

1

อีกทางเลือกหนึ่งคือการเขียนข้อ จำกัด แบบกำหนดเองโดยใช้ConstraintคลาสNUnit abstract ด้วยคลาสตัวช่วยในการให้น้ำตาลซินแทติกเล็กน้อยโค้ดการทดสอบที่ได้จะมีความกระชับและอ่านได้เช่น

Assert.That( LeftObject, PortfolioState.Matches( RightObject ) ); 

ตัวอย่างเช่นพิจารณาคลาสที่มีสมาชิก 'อ่านอย่างเดียว' ไม่ใช่IEquatableและคุณไม่สามารถเปลี่ยนชั้นเรียนภายใต้การทดสอบแม้ว่าคุณต้องการ:

public class Portfolio // Somewhat daft class for pedagogic purposes...
{
    // Cannot be instanitated externally, instead has two 'factory' methods
    private Portfolio(){ }

    // Immutable properties
    public string Property1 { get; private set; }
    public string Property2 { get; private set; }  // Cannot be accessed externally
    public string Property3 { get; private set; }  // Cannot be accessed externally

    // 'Factory' method 1
    public static Portfolio GetPortfolio(string p1, string p2, string p3)
    {
        return new Portfolio() 
        { 
            Property1 = p1, 
            Property2 = p2, 
            Property3 = p3 
        };
    }

    // 'Factory' method 2
    public static Portfolio GetDefault()
    {
        return new Portfolio() 
        { 
            Property1 = "{{NONE}}", 
            Property2 = "{{NONE}}", 
            Property3 = "{{NONE}}" 
        };
    }
}

สัญญาสำหรับConstraintชั้นเรียนกำหนดให้หนึ่งในการลบล้างMatchesและWriteDescriptionTo(ในกรณีที่ไม่ตรงกันคำบรรยายสำหรับค่าที่คาดหวัง) แต่ยังมีการลบล้างWriteActualValueTo(การเล่าเรื่องสำหรับมูลค่าจริง) ก็สมเหตุสมผล:

public class PortfolioEqualityConstraint : Constraint
{
    Portfolio expected;
    string expectedMessage = "";
    string actualMessage = "";

    public PortfolioEqualityConstraint(Portfolio expected)
    {
        this.expected = expected;
    }

    public override bool Matches(object actual)
    {
        if ( actual == null && expected == null ) return true;
        if ( !(actual is Portfolio) )
        { 
            expectedMessage = "<Portfolio>";
            actualMessage = "null";
            return false;
        }
        return Matches((Portfolio)actual);
    }

    private bool Matches(Portfolio actual)
    {
        if ( expected == null && actual != null )
        {
            expectedMessage = "null";
            expectedMessage = "non-null";
            return false;
        }
        if ( ReferenceEquals(expected, actual) ) return true;

        if ( !( expected.Property1.Equals(actual.Property1)
                 && expected.Property2.Equals(actual.Property2) 
                 && expected.Property3.Equals(actual.Property3) ) )
        {
            expectedMessage = expected.ToStringForTest();
            actualMessage = actual.ToStringForTest();
            return false;
        }
        return true;
    }

    public override void WriteDescriptionTo(MessageWriter writer)
    {
        writer.WriteExpectedValue(expectedMessage);
    }
    public override void WriteActualValueTo(MessageWriter writer)
    {
        writer.WriteExpectedValue(actualMessage);
    }
}

รวมถึงคลาสผู้ช่วย:

public static class PortfolioState
{
    public static PortfolioEqualityConstraint Matches(Portfolio expected)
    {
        return new PortfolioEqualityConstraint(expected);
    }

    public static string ToStringForTest(this Portfolio source)
    {
        return String.Format("Property1 = {0}, Property2 = {1}, Property3 = {2}.", 
            source.Property1, source.Property2, source.Property3 );
    }
}

ตัวอย่างการใช้งาน:

[TestFixture]
class PortfolioTests
{
    [Test]
    public void TestPortfolioEquality()
    {
        Portfolio LeftObject 
            = Portfolio.GetDefault();
        Portfolio RightObject 
            = Portfolio.GetPortfolio("{{GNOME}}", "{{NONE}}", "{{NONE}}");

        Assert.That( LeftObject, PortfolioState.Matches( RightObject ) );
    }
}

1

ฉันจะสร้างคำตอบของ @Juanma อย่างไรก็ตามฉันเชื่อว่าสิ่งนี้ไม่ควรนำไปใช้กับการยืนยันการทดสอบหน่วย นี่คือยูทิลิตี้ที่สามารถใช้งานได้ดีในบางสถานการณ์โดยรหัสที่ไม่ได้ทดสอบ

ฉันเขียนบทความเกี่ยวกับเรื่องนี้http://timoch.com/blog/2013/06/unit-test-equality-is-not-domain-equality/

ข้อเสนอของฉันมีดังต่อไปนี้:

/// <summary>
/// Returns the names of the properties that are not equal on a and b.
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
/// <returns>An array of names of properties with distinct 
///          values or null if a and b are null or not of the same type
/// </returns>
public static string[] GetDistinctProperties(object a, object b) {
    if (object.ReferenceEquals(a, b))
        return null;
    if (a == null)
        return null;
    if (b == null)
        return null;

    var aType = a.GetType();
    var bType = b.GetType();

    if (aType != bType)
        return null;

    var props = aType.GetProperties();

    if (props.Any(prop => prop.GetIndexParameters().Length != 0))
        throw new ArgumentException("Types with index properties not supported");

    return props
        .Where(prop => !Equals(prop.GetValue(a, null), prop.GetValue(b, null)))
        .Select(prop => prop.Name).ToArray();
} 

โดยใช้สิ่งนี้กับ NUnit

Expect(ReflectionUtils.GetDistinctProperties(tile, got), Empty);

ให้ข้อความต่อไปนี้ไม่ตรงกัน

Expected: <empty>
But was:  < "MagmaLevel" >
at NUnit.Framework.Assert.That(Object actual, IResolveConstraint expression, String message, Object[] args)
at Undermine.Engine.Tests.TileMaps.BasicTileMapTests.BasicOperations() in BasicTileMapTests.cs: line 29

1

https://github.com/kbilsted/StatePrinterถูกเขียนขึ้นโดยเฉพาะเพื่อถ่ายโอนข้อมูลกราฟออบเจ็กต์ไปยังการแสดงสตริงโดยมีจุดประสงค์เพื่อเขียนแบบทดสอบหน่วยง่าย

  • มันมาพร้อมกับวิธีการ Assert ที่ส่งออกสตริงที่หลีกเลี่ยงอย่างถูกต้องคัดลอกวางลงในการทดสอบเพื่อแก้ไขได้อย่างง่ายดาย
  • ช่วยให้สามารถเขียนซ้ำได้โดยอัตโนมัติ
  • รวมเข้ากับกรอบการทดสอบหน่วยทั้งหมด
  • ไม่เหมือนกับการทำให้อนุกรม JSON รองรับการอ้างอิงแบบวงกลม
  • คุณสามารถกรองได้อย่างง่ายดายดังนั้นจึงทิ้งเฉพาะบางส่วนของประเภทเท่านั้น

ป.ร. ให้ไว้

class A
{
  public DateTime X;
  public DateTime Y { get; set; }
  public string Name;
}

คุณสามารถในลักษณะที่ปลอดภัยและใช้การเติมเต็มอัตโนมัติของ Visual Studio รวมหรือไม่รวมฟิลด์

  var printer = new Stateprinter();
  printer.Configuration.Projectionharvester().Exclude<A>(x => x.X, x => x.Y);

  var sut = new A { X = DateTime.Now, Name = "Charly" };

  var expected = @"new A(){ Name = ""Charly""}";
  printer.Assert.PrintIsSame(expected, sut);

1

เพียงแค่ติดตั้ง expectedObjects จาก Nuget คุณสามารถเปรียบเทียบค่าคุณสมบัติของวัตถุสองอย่างได้อย่างง่ายดายค่าวัตถุแต่ละชิ้นของคอลเลกชันค่าของวัตถุที่ประกอบด้วยสองค่าและค่าคุณสมบัติเปรียบเทียบบางส่วนตามประเภทที่ไม่ระบุชื่อ

ฉันมีตัวอย่างใน github: https://github.com/hatelove/CompareObjectEquals

นี่คือตัวอย่างบางส่วนที่มีสถานการณ์เปรียบเทียบวัตถุ:

    [TestMethod]
    public void Test_Person_Equals_with_ExpectedObjects()
    {
        //use extension method ToExpectedObject() from using ExpectedObjects namespace to project Person to ExpectedObject
        var expected = new Person
        {
            Id = 1,
            Name = "A",
            Age = 10,
        }.ToExpectedObject();

        var actual = new Person
        {
            Id = 1,
            Name = "A",
            Age = 10,
        };

        //use ShouldEqual to compare expected and actual instance, if they are not equal, it will throw a System.Exception and its message includes what properties were not match our expectation.
        expected.ShouldEqual(actual);
    }

    [TestMethod]
    public void Test_PersonCollection_Equals_with_ExpectedObjects()
    {
        //collection just invoke extension method: ToExpectedObject() to project Collection<Person> to ExpectedObject too
        var expected = new List<Person>
        {
            new Person { Id=1, Name="A",Age=10},
            new Person { Id=2, Name="B",Age=20},
            new Person { Id=3, Name="C",Age=30},
        }.ToExpectedObject();

        var actual = new List<Person>
        {
            new Person { Id=1, Name="A",Age=10},
            new Person { Id=2, Name="B",Age=20},
            new Person { Id=3, Name="C",Age=30},
        };

        expected.ShouldEqual(actual);
    }

    [TestMethod]
    public void Test_ComposedPerson_Equals_with_ExpectedObjects()
    {
        //ExpectedObject will compare each value of property recursively, so composed type also simply compare equals.
        var expected = new Person
        {
            Id = 1,
            Name = "A",
            Age = 10,
            Order = new Order { Id = 91, Price = 910 },
        }.ToExpectedObject();

        var actual = new Person
        {
            Id = 1,
            Name = "A",
            Age = 10,
            Order = new Order { Id = 91, Price = 910 },
        };

        expected.ShouldEqual(actual);
    }

    [TestMethod]
    public void Test_PartialCompare_Person_Equals_with_ExpectedObjects()
    {
        //when partial comparing, you need to use anonymous type too. Because only anonymous type can dynamic define only a few properties should be assign.
        var expected = new
        {
            Id = 1,
            Age = 10,
            Order = new { Id = 91 }, // composed type should be used anonymous type too, only compare properties. If you trace ExpectedObjects's source code, you will find it invoke config.IgnoreType() first.
        }.ToExpectedObject();

        var actual = new Person
        {
            Id = 1,
            Name = "B",
            Age = 10,
            Order = new Order { Id = 91, Price = 910 },
        };

        // partial comparing use ShouldMatch(), rather than ShouldEqual()
        expected.ShouldMatch(actual);
    }

อ้างอิง:

  1. Github ที่คาดไว้
  2. บทนำของ expectedObjects


1

ฉันจบลงด้วยการเขียนโรงงานนิพจน์ทั่วไป:

public static class AllFieldsEqualityComprision<T>
{
    public static Comparison<T> Instance { get; } = GetInstance();

    private static Comparison<T> GetInstance()
    {
        var type = typeof(T);
        ParameterExpression[] parameters =
        {
            Expression.Parameter(type, "x"),
            Expression.Parameter(type, "y")
        };
        var result = type.GetProperties().Aggregate<PropertyInfo, Expression>(
            Expression.Constant(true),
            (acc, prop) =>
                Expression.And(acc,
                    Expression.Equal(
                        Expression.Property(parameters[0], prop.Name),
                        Expression.Property(parameters[1], prop.Name))));
        var areEqualExpression = Expression.Condition(result, Expression.Constant(0), Expression.Constant(1));
        return Expression.Lambda<Comparison<T>>(areEqualExpression, parameters).Compile();
    }
}

และใช้มัน:

Assert.That(
    expectedCollection, 
    Is.EqualTo(actualCollection)
      .Using(AllFieldsEqualityComprision<BusinessCategoryResponse>.Instance));

มันมีประโยชน์มากเพราะฉันต้องเปรียบเทียบคอลเลกชันของวัตถุดังกล่าว และคุณสามารถใช้การเปรียบเทียบนี้ได้ที่อื่น :)

นี่คือส่วนสำคัญพร้อมตัวอย่าง: https://gist.github.com/Pzixel/b63fea074864892f9aba8ffde312094f


0

ยกเลิกการเชื่อมต่อคลาสทั้งสองคลาสและทำการเปรียบเทียบสตริง

แก้ไข: ทำงานได้อย่างสมบูรณ์นี่คือผลลัพธ์ที่ฉันได้รับจาก NUnit

Test 'Telecom.SDP.SBO.App.Customer.Translator.UnitTests.TranslateEaiCustomerToDomain_Tests.TranslateNew_GivenEaiCustomer_ShouldTranslateToDomainCustomer_Test("ApprovedRatingInDb")' failed:
  Expected string length 2841 but was 5034. Strings differ at index 443.
  Expected: "...taClasses" />\r\n  <ContactMedia />\r\n  <Party i:nil="true" /..."
  But was:  "...taClasses" />\r\n  <ContactMedia>\r\n    <ContactMedium z:Id="..."
  ----------------------------------------------^
 TranslateEaiCustomerToDomain_Tests.cs(201,0): at Telecom.SDP.SBO.App.Customer.Translator.UnitTests.TranslateEaiCustomerToDomain_Tests.Assert_CustomersAreEqual(Customer expectedCustomer, Customer actualCustomer)
 TranslateEaiCustomerToDomain_Tests.cs(114,0): at Telecom.SDP.SBO.App.Customer.Translator.UnitTests.TranslateEaiCustomerToDomain_Tests.TranslateNew_GivenEaiCustomer_ShouldTranslateToDomainCustomer_Test(String custRatingScenario)

แก้ไขสอง: วัตถุทั้งสองสามารถเหมือนกันได้ แต่ลำดับที่คุณสมบัติถูกทำให้เป็นอนุกรมไม่เหมือนกัน ดังนั้น XML จึงแตกต่างกัน DOH!

แก้ไขสาม: ใช้งานได้ ฉันใช้มันในการทดสอบของฉัน แต่คุณต้องเพิ่มไอเท็มในคุณสมบัติคอลเลกชันตามลำดับรหัสที่อยู่ระหว่างการทดสอบจะเพิ่มเข้าไป


1
ทำให้เป็นอนุกรม ? ความคิดที่น่าสนใจ ฉันไม่แน่ใจว่ามันจะเป็นอย่างไรในแง่ของประสิทธิภาพ
Michael Haren

ไม่อนุญาตให้คุณเปรียบเทียบคู่ผสมหรือทศนิยมด้วยความแม่นยำที่กำหนด
Noctis

0

ฉันรู้ว่านี่เป็นคำถามเก่าจริงๆ แต่ NUnit ยังไม่มีการสนับสนุนแบบเนทีฟสำหรับคำถามนี้ อย่างไรก็ตามหากคุณชอบการทดสอบสไตล์ BDD (อลาจัสมิน) คุณจะต้องประหลาดใจกับ NExpect ( https://github.com/fluffynuts/NExpectรับจาก NuGet) ซึ่งมีการทดสอบความเท่าเทียมกันอย่างลึกซึ้งในที่นั่น .

(ข้อจำกัดความรับผิดชอบ: ฉันเป็นผู้เขียน NExpect)


-1

สตริงและเปรียบเทียบสองสตริง

Assert.AreEqual (JSON.stringify (LeftObject), JSON.stringify (RightObject))


-1
//Below works precisely well, Use it.
private void CompareJson()
{
object expected = new object();
object actual = new object();
var serializer = new System.Web.Script.Serialization.JavaScriptSerializer();
var expectedResponse = serializer.Serialize(expected);
var actualResponse = serializer.Serialize(actual);
Assert.AreEqual(expectedResponse, actualResponse);
}

ขอขอบคุณสำหรับข้อมูลโค้ดนี้ซึ่งอาจให้ความช่วยเหลือระยะสั้นที่ จำกัด คำอธิบายที่เหมาะสมจะช่วยเพิ่มมูลค่าในระยะยาวได้อย่างมากโดยแสดงให้เห็นว่าเหตุใดจึงเป็นวิธีแก้ปัญหาที่ดีและจะทำให้มีประโยชน์มากขึ้นสำหรับผู้อ่านในอนาคตที่มีคำถามอื่น ๆ ที่คล้ายคลึงกัน โปรดแก้ไขคำตอบของคุณเพื่อเพิ่มคำอธิบายรวมถึงสมมติฐานที่คุณตั้งขึ้น
Toby Speight

และสิ่งนี้เพิ่มอะไรให้กับคำตอบของ Max ?
Toby Speight
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.