我试图断言一个对象与另一个对象"相等".
对象只是具有一堆公共属性的类的实例.是否有一种简单的方法让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验证两个集合的内容是否相同.
如果由于任何原因无法覆盖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.这很乏味并影响域逻辑.代替,
您的对象没有其他逻辑.没有额外的测试任务.
只需使用这个简单的方法:
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();
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,然后在单元测试中,您可以简单地执行此操作:
Assert.AreEqual(LeftObject, RightObject);
当然,这可能意味着您只需将所有单独的比较移动到.Equals方法,但它允许您将该实现重用于多个测试,并且可能有必要让对象应该能够与兄弟姐妹进行比较.
我不想仅仅为了启用测试而重写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{ 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,你会很高兴以后(如果你赞成这一点,请同时提出dkl 的回答,首先建议使用FluentAssertions).
我同意ChrisYoxall - 在主要代码中实现Equals仅仅是为了测试目的并不好.
如果你正在实现Equals,因为某些应用程序逻辑需要它,那么这很好,但保持纯粹的仅测试代码的混乱(也是检查相同的测试语义可能与你的应用程序需要的不同).
简而言之,保持您的课程中仅测试代码.
对于大多数类,使用反射的属性的简单浅层比较应该足够了,尽管如果对象具有复杂属性,则可能需要递归.如果参考,请注意循环引用或类似内容.
狡猾
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);