创建单元测试以确保不变性

时间:2011-03-24 09:29:12

标签: c# unit-testing nunit immutability

我设计了一个immutable class,因为我希望它具有值语义。我在课堂的评论部分写了一个提示

// "This class is immutable, don't change this when adding new features to it."

但我知道,有时这些评论会被其他团队成员忽略,所以我想创建一个单元测试作为额外的保护措施。知道怎么做到这一点?可以通过反射检查一个类,以确保只有构造函数改变它的内部状态吗?

(使用C#2.0和NUnit,如果这对任何人都很重要)。

4 个答案:

答案 0 :(得分:10)

备份我关于如何递归使用FieldInfo.IsInitOnly来测试不变性的评论的示例。

可能会有更多特殊情况需要考虑,例如我处理string的方式,但它只会给出我认为的假阴性,即会告诉你一些不可变的东西,而不是反过来。< / p>

逻辑是,每个字段必须是readonly并且本身是不可变类型。请注意,它不能处理自引用类型或循环引用。

using System;
using System.Linq;
using System.Reflection;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace ImmutableTests
{
    [TestClass]
    public class AssertImmutableTests
    {
        [TestMethod]
        public void Is_int_immutable()
        {
            Assert.IsTrue(Immutable<int>());
        }

        [TestMethod]
        public void Is_string_immutable()
        {
            Assert.IsTrue(Immutable<string>());
        }

        [TestMethod]
        public void Is_custom_immutable()
        {
            Assert.IsTrue(Immutable<MyImmutableClass>());
        }

        [TestMethod]
        public void Is_custom_mutable()
        {
            Assert.IsFalse(Immutable<MyMutableClass>());
        }

        [TestMethod]
        public void Is_custom_deep_mutable()
        {
            Assert.IsFalse(Immutable<MyDeepMutableClass>());
        }

        [TestMethod]
        public void Is_custom_deep_immutable()
        {
            Assert.IsTrue(Immutable<MyDeepImmutableClass>());
        }

        [TestMethod]
        public void Is_propertied_class_mutable()
        {
            Assert.IsFalse(Immutable<MyMutableClassWithProperty>());
        }

        private static bool Immutable<T>()
        {
            return Immutable(typeof(T));
        }

        private static bool Immutable(Type type)
        {
            if (type.IsPrimitive) return true;
            if (type == typeof(string)) return true;
            var fieldInfos = type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
            var isShallowImmutable = fieldInfos.All(f => f.IsInitOnly);
            if (!isShallowImmutable) return false;
            var isDeepImmutable = fieldInfos.All(f => Immutable(f.FieldType));
            return isDeepImmutable;
        }
    }

    public class MyMutableClass
    {
        private string _field;
    }

    public class MyImmutableClass
    {
        private readonly string _field;
    }

    public class MyDeepMutableClass
    {
        private readonly MyMutableClass _field;
    }

    public class MyDeepImmutableClass
    {
        private readonly MyImmutableClass _field;
    }

    public class MyMutableClassWithProperty
    {
        public string Prop { get; set; }
    }
}

答案 1 :(得分:8)

您可以检查该类是否已密封,并使用反射检查每个字段是否为只读(使用FieldInfo.IsInitOnly)。

当然,这只能确保不变性 - 它不会阻止某人在其中放置List<int>字段,然后更改列表的内容。

答案 2 :(得分:3)

这篇文章有一些想法。虽然,似乎没有万无一失的方式。

How do I find out if a class is immutable in C#

答案 3 :(得分:2)

不确定您是否听说过NDepend,但此工具允许您“反省”源代码和编译的程序集,并执行各种魔术,包括依赖性检查等等。

其中一项检查是对不变性的检查。例如,我有一个IImmutable标记接口,如果任何类型具有此接口但是可变,NDepend将失败我的构建,使用以下查询:

WARN IF Count > 0 IN SELECT TYPES WHERE 
  Implement "MyCompany.MyAssemblies.Dto.IImmutable" AND
  !IsImmutable

您还可以将其配置为生成违规报告以及失败的版本。

显然这实际上不是单元测试。但是,它可以作为构建的一部分进行集成,并且无法像单元测试那样使构建失败,所以我想我会提到它!

请参阅here了解更多信息,了解其实际用途和方式。