我设计了一个immutable class,因为我希望它具有值语义。我在课堂的评论部分写了一个提示
// "This class is immutable, don't change this when adding new features to it."
但我知道,有时这些评论会被其他团队成员忽略,所以我想创建一个单元测试作为额外的保护措施。知道怎么做到这一点?可以通过反射检查一个类,以确保只有构造函数改变它的内部状态吗?
(使用C#2.0和NUnit,如果这对任何人都很重要)。
答案 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)
这篇文章有一些想法。虽然,似乎没有万无一失的方式。
答案 3 :(得分:2)
不确定您是否听说过NDepend,但此工具允许您“反省”源代码和编译的程序集,并执行各种魔术,包括依赖性检查等等。
其中一项检查是对不变性的检查。例如,我有一个IImmutable
标记接口,如果任何类型具有此接口但是可变,NDepend将失败我的构建,使用以下查询:
WARN IF Count > 0 IN SELECT TYPES WHERE
Implement "MyCompany.MyAssemblies.Dto.IImmutable" AND
!IsImmutable
您还可以将其配置为生成违规报告以及失败的版本。
显然这实际上不是单元测试。但是,它可以作为构建的一部分进行集成,并且无法像单元测试那样使构建失败,所以我想我会提到它!
请参阅here了解更多信息,了解其实际用途和方式。