2014-09-19 19 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