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

Doc*_*own 6 c# nunit unit-testing immutability

我设计了一个不可变类,因为我希望它具有值语义.我在课堂的评论部分写了一个提示

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

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

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

wes*_*ton 10

一个示例来备份我对如何FieldInfo.IsInitOnly递归地测试不变性的评论.

可能会有更多特殊情况需要考虑,比如我如何处理string,但它只会给出我认为的假阴性,即会告诉你一些不可变的东西,而不是相反.

逻辑是,每个字段必须是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; }
    }
}
Run Code Online (Sandbox Code Playgroud)


Jon*_*eet 8

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

当然,这只能确保浅层的不变性 - 它不会阻止某人List<int>在那里放置一个字段,然后改变列表的内容.