2011-03-24 50 views
5

我設計了一個immutable class,因爲我想爲它創造價值語義。我寫了一個提示進級創建用於確保不變性的單元測試

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

但我知道,有時候這些評論是由其他團隊成員忽視的評論部分,所以我想創建單元測試作爲額外的保障。任何想法如何做到這一點?我們可以通過反射來檢查一個類,以確保只有構造函數改變它的內部狀態嗎?

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

+0

對匿名downvoter:當你認爲這個問題有什麼問題,請通知我。 – 2014-09-13 22:02:55

回答

8

舉例說明如何使用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; } 
    } 
} 
+0

好的方法。謝謝。 – denver 2014-03-07 19:03:50

+0

也許我是看到錯誤的東西,但是一個包含public int的類在這個例子中被認爲是不可變的,對嗎?雖然它實際上是可變的。 – 2016-02-25 12:47:14

+0

@TonniTielens取決於你的意思,'public int A {get; private set;}'mutable,'public int A {get;}'不可變。 '公衆詮釋A;'可變。 – weston 2016-02-25 14:47:25

3

這篇文章有一些想法。雖然,似乎沒有確定的方法。

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

+0

+1,這個答案也是有用的,但對於我的規則我只能接受一個。 – 2011-03-24 11:54:33

+1

「只能有一個!「並不常見,因爲SO:p – MattC 2011-03-24 12:30:59

7

你可以檢查這個類是密封的,並且使用反射的支票,每個字段(使用FieldInfo.IsInitOnly)只讀。

當然,這既保證了永恆 - 它不會從投入有一個List<int>字段,然後更改列表的內容阻止別人。

+0

+1並且被接受(我可以與95%的解決方案一起生活) – 2011-03-24 11:53:57

+0

好主意這個,我用這個來創建一個'AssertImmutable ()'方法,你可以請在字段類型上調用recusively來聲明深度不變性。 – weston 2013-06-17 15:14:46

+0

@weston你是否分享了這個? – denver 2014-03-06 20:32:34

2

不知道你是否聽說過NDepend,但是這個工具可以讓你「反省」你的源代碼和編譯後的程序集,並且執行各種魔法,包括依賴性檢查等等。

一個這樣的檢查是對不變性的檢查。舉例來說,我有一個IImmutable標記接口,和NDepend的失敗,我的生成,如果任何類型都有這個接口,但是是可變的,使用下面的查詢:

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

您還可以配置它產生違規報告,以及失敗的構建。

顯然這實際上不是一個單元測試。但是,它可以作爲構建的一部分進行集成,並且可以像單元測試一樣使構建失敗,所以我想我會提及它!

有關更多信息,請參閱here它的實際功能和方式。

+0

+1,這個答案也很有用,但是我只能接受一個。也許我會試試看NDepend是否值得我們項目的價格。 – 2011-03-24 11:59:47