在单元测试中向对象断言未经验证的添加属性

本文关键字:验证 添加 属性 断言 单元测试 对象 | 更新日期: 2023-09-27 18:03:53

我有MSTest (VS 2012)单元测试,我想断言对象的各种属性具有我想要的值。有很多方法可以做到这一点。然而,我主要担心的是,如果一个新属性被添加到对象中,很容易忽略更新单元测试以确保它具有我们期望的值。

我能想到的一件事是使用反射来枚举对象的公共属性,然后跟踪单元测试断言了哪些属性,最后断言是否有任何属性没有被检查。

有没有人写过类似的东西?

有更好的主意吗?

更新:我应该指出,所讨论的对象类似于数据传输对象,其中有其他类/方法导致该对象中的数据被更新。我们很容易忽略为那些类/方法更新测试以确保我们考虑到所有对象的属性。我想要一些更强大的东西(即不能被遗忘或忽视),而不是右键单击对象,查找引用,并检查代码。

例如:

public class Person {
  public string FirstName;
}
public Person GetPerson() {}
[TestMethod]
public void GetPerson_ReturnsFilledInPerson()
{
    var actual = target.GetPerson();
    Assert.IsNotNull(actual.FirstName);
    // If somebody later adds LastName to Person, 
    // we want this unit test to fail until the LastName is checked too.
}

谢谢,丹

在单元测试中向对象断言未经验证的添加属性

你们的要求并非不合理。对于简单的Domain类到DTO的映射,可以使用Automapper这样的框架。你只要告诉它域类A映射到dto,它就会使用约定而不是配置来映射属性。该框架还附带了一种方法来测试您是否已经完成了所有映射。您可以调用Mapper.AssertConfigurationIsValid();,它将确保Dto中的每个属性都映射到域类中的属性(代码中的显式或具有相同属性名称的隐式)。

所以基本上你在尝试做同样的事情,这就是为什么它是一个非常有效的场景。假设你选择不使用Automapper。我猜你的代码中没有单独的mapper(在这种情况下,你可以使用反射来测试你的映射器类/代码)。我做过类似的事情,所有的dto实现一个DeepCopy方法,返回另一个IDto。但我们希望实际类型与调用类型

相同
    [Test]
    public void DeepCopyReturnsSameTypeAsOriginal()
    {
        var iDtoType = typeof (IDto);
        var allIDtoTypes = iDtoType.Assembly.GetTypes().Where(t => iDtoType.IsAssignableFrom(t) && t.IsClass && !t.IsAbstract).ToList();
        foreach (var currentIDtoType in allIDtoTypes)
        {
            var instanceOfDtoType = (IDto)Activator.CreateInstance(currentIDtoType);
            var deepCopyType = instanceOfDtoType.DeepCopy();
            Assert.AreEqual(instanceOfDtoType.GetType(), deepCopyType.GetType(), 
                                string.Format("Deep Copy of Type '{0}' does not return the same Type. It returns '{1}'. The DeepCopy method of IDto should return the same type.",
                                instanceOfDtoType.GetType(), deepCopyType.GetType()));
        }
    }

您可以使用类似的逻辑来获取person对象。获取person object的所有公共属性并遍历它们,并断言您返回的具体person对象包含该特定属性的非空值。

尽管在这种情况下,测试必须仅用于非常特定的对象,您知道所有属性必须具有非空值。例如,对于值类型,如果AccountBalance属性返回0,那么这个人的余额是0还是没有设置该属性?如果这个人的email属性是空的,这是否意味着它没有被映射或者我们只是没有这个人的email在系统中。这就是为什么如果你有特定的映射器类或使用Automapper,那么你更具体地测试映射是否存在,而不是"这是否有一个值",正如我所说的,这可能仍然适用于你的应用程序中的一些有限的类。

另一个选择是装饰属性,你知道不能为空,必须与自定义属性映射。例如

Public class PersonDto {
    [TestNotNull]
    public string FirstName { get; set}
}

然后在你的单元测试中,你可以使用反射来找到所有带有这个属性的属性,并确保它们不是空的。当然,你会遇到同样的问题,你试图在不同的层面上解决。如果您担心人们会忘记映射重要的字段,那么就不能保证他们会记得用自定义属性来修饰这些字段。

当然,您可能不想测试属性是否被映射,而只想测试属性是否存在。在这种情况下,使用反射来获得域对象类型的所有属性列表,并确保它们中的每一个都存在于DTO对象类型的属性列表中。但是这并没有测试属性本身是否被映射,而只是测试它们是否存在。如果您使用Automapper和它的AssertConfigurationIsValid()测试,您可以测试DTO中的每个属性是否被正确映射,而不是域中的每个属性在DTO中都有一个相关的属性。

所以要回答你的问题,如果你想测试属性是否存在并且它是否被正确映射,你需要两个测试。一个是比较域对象类型和Dto对象类型,并确保其中一个对象的属性存在于另一个对象中,另一个是确保Dto中的所有属性都被映射到某些东西(无论您是在自己的代码中进行映射还是使用Automapper之类的框架)。