2014-09-19 82 views
2

我是NSubstitue的新手(在.NET中對單元測試頗爲新穎)。我想測試我的課程是否將所有數據保存在不同文件中,例如, StringDictionary。我怎樣才能用NSubstitute僞造當前類的方法?

說我有我的DataManipulation.cs類:

using System; 
using System.Collections; 
using System.Collections.Specialized; 

namespace ApplicationName 
{ 
    // interface for NSubstitute 
    public interface IManipulator 
    { 
     void saveAllData(); 
     void saveEntry(string entryKey, string entryValue); 
    } 

    public class DataManipulator : IManipulator 
    { 
     protected StringDictionary _data {get; private set;} 

     public DataManipulator() 
     { 
      _data = new StringDictionary(); 
     } 

     public void addData(string name, string data) 
     { 
      this._data.Add(name, data); 
     } 

     public void saveAllData() 
     { 
      // potential implementation - I want to test this 
      foreach (DictionaryEntry entry in this._data) 
      { 
       this.saveEntry(entry.Key.ToString(), entry.Value.ToString()); 
      } 
     } 

     public void saveEntry(string entryKey, string entryValue) 
     { 
      // interact with filesystem, save each entry in its own file 
     } 
    } 
} 

我想測試:當我打電話DataManipulator.saveAllData()它保存在一個單獨的文件中的每個_data項 - 這意味着它運行saveEntry的次數等於_data.Count。 NSubstitute可以嗎?

每次我嘗試使用DataManipulation作爲測試對象,並單獨作爲模擬 - 當我運行Received()我有信息,沒有進行調用。

NUnit測試模板,我想用:

using System; 
using System.Collections.Generic; 

using NUnit.Framework; 
using NSubstitute; 

namespace ApplicationName.UnitTests 
{ 
    [TestFixture] 
    class DataManipulatorTests 
    { 
     [Test] 
     public void saveAllData_CallsSaveEntry_ForEachData() 
     { 

      DataManipulator dm = new DataManipulator(); 
      dm.addData("abc", "abc"); 
      dm.addData("def", "def"); 
      dm.addData("ghi", "ghi"); 

      dm.saveAllData(); 

      // how to assert if it called DataManipulator.saveEntry() three times? 
     } 

    } 
} 

或者我應該做它在不同的方式?

回答

3

根據一些OOP原則和測試需求,您必須引入一個依賴或某種構造來創建適合測試的「接縫」。

另一個依賴使用的模擬

這將封裝的數據存儲,你會檢查你的斷言反對。我建議你閱讀假,存根和模擬之間的區別。

  1. 添加新的存儲接口和實現。

    public interface IDataStorage 
    { 
        void Store(string key, string value); 
    } 
    
    public class DataStorage : IDataStorage 
    { 
        public void Store(string key, string value) 
        { 
         //some usefull logic 
        } 
    } 
    
  2. 在你的手實現使用它作爲依賴(並通過構造函數注入)

    public class DataManipulator : IManipulator 
    { 
        protected IDataStorage _storage { get; private set; } 
        protected StringDictionary _data { get; private set; } 
    
        public DataManipulator(IDataStorage storage) 
        { 
         _storage = storage; 
         _data = new StringDictionary(); 
        } 
    
        public void addData(string name, string data) 
        { 
         this._data.Add(name, data); 
        } 
    
        public void saveAllData() 
        {     
         // potential implementation - I want to test this 
         foreach (DictionaryEntry entry in this._data) 
         { 
          this.saveEntry(entry.Key.ToString(), entry.Value.ToString()); 
         } 
        } 
    
        public void saveEntry(string entryKey, string entryValue) 
        { 
         _storage.Store(entryKey, entryValue); 
        } 
    } 
    
  3. 測試它

    [Test] 
    public void saveAllData_CallsSaveEntry_ForEachData() 
    { 
    
        var dataStorageMock = Substitute.For<IDataStorage>(); 
        DataManipulator dm = new DataManipulator(dataStorageMock); 
        dm.addData("abc", "abc"); 
        dm.addData("def", "def"); 
        dm.addData("ghi", "ghi"); 
    
        dm.saveAllData(); 
    
        dataStorageMock.Received().Store("abc", "abc"); 
        dataStorageMock.Received().Store("def", "def"); 
        dataStorageMock.Received().Store("ghi", "ghi"); 
        //or 
        dataStorageMock.Received(3).Store(Arg.Any<string>(), Arg.Any<string>()); 
    } 
    

這裏最重要的是你有沒有測試私人方法調用。這是一個不好的做法!單元測試主要是測試公共合同,而不是私有方法,這些方法在時間上更加可以改變。 (對不起,我錯過saveEntry(..)是公開的)

DataManipulator的使用作爲假

我認爲這不是一個好主意,但是......這樣做與NSubstitute的唯一途徑是使方法saveEntry虛:

public virtual void saveEntry(string entryKey, string entryValue) 
{ 
//something useful 
} 

並對其進行測試:

[Test] 
public void saveAllData_CallsSaveEntry_ForEachData() 
{ 

    var dm = Substitute.For<DataManipulator>(); 
    dm.addData("abc", "abc"); 
    dm.addData("def", "def"); 
    dm.addData("ghi", "ghi"); 

    dm.saveAllData(); 

    dm.Received(3).saveEntry(Arg.Any<string>(), Arg.Any<string>()); 
} 

需要做一些方法只是用於測試的虛擬需求可能不是很有吸引力,但..

  1. 只要您的測試也是您的業務邏輯的客戶端,就可以接受它。
  2. 在這種情況下,可以使用一些像MS Fakes這樣的「重」測試框架,但它似乎是一種矯枉過正。
  3. 另一種解決方案是測試另一個工作單元,其中涵蓋了一個工作單元(可能看起來像我的第一個解決方案)。

UPD:讀取它http://nsubstitute.github.io/help/partial-subs/更好地瞭解NSubstitute。

+0

謝謝!很棒 – BartekR 2014-09-20 19:50:34