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

В соответствии с некоторыми принципами ООП и потребностями тестирования вам необходимо представить зависимость или некоторую конструкцию, чтобы создать «шов», который подходит для тестирования.

Использование другой зависимости в макете

Это будет инкапсулировать хранение данных и вы будете проверять свои утверждения против него. Я рекомендую вам прочитать о том, какая разница между фальшивкой, заглушкой и макетом.

  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

 Смежные вопросы

  • Нет связанных вопросов^_^