2013-03-27 4 views
2

У меня есть интерфейс, который используется в контроллере MVC, который получает некоторые данные. Для того, чтобы сохранить его простым интерфейсом до сих пор выглядит примерно так:Интерфейс модуля тестирования при реализации

public interface IDataProvider 
{ 
    DataModel GetData(); 
} 

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

Для этого у меня есть интерфейс регистратора, который на самом деле является интерфейсом для NLog под названием ILogger. Я мог бы это сделать:

public interface IDataProvider 
{ 
    DataModel GetData(ILogger logger); 
} 

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

Лучший способом я могу думать прямо сейчас включить регистратор в конструкторе моей реализации, которые могли бы выглядеть следующим образом:

public class DataProvider : IDataProvider 
{ 
    private readonly ILogger _logger; 

    public DataProvider(ILogger logger) 
    { 
     _logger = logger; 
    } 

    public DataModel GetData() 
    { 
     // CODE GOES HERE 
    } 
} 

Однако это означает, что я не могу проверить регистратор в моих модульных тестах , Каков наилучший способ достичь этого, чтобы я мог оставить регистратор отдельно от метода и сделать его пригодным для проверки?

Буду признателен за любую помощь, спасибо.

EDIT:

Я понимаю, что я пропустил блок кода тестирования здесь является то, что я имею в виду:

На данный момент я гарантируя, что GetData называется в моем действии этот путь:

var controller = new DataController(_dataProvider.Object); 

controller.Index(); 

_dataProvider.Verify(dataProvider => dataProvider.GetData()); 

То, что я хотел бы сделать, это то же самое, что и для регистратора, но только в том случае, если исключение выбрано следующим образом:

_dataProvider.Setup(dataProvider => dataProvider.GetData()).Throws<WebException>(); 

var controller = new DataController(_dataProvider.Object); 

controller.Index(); 

_logger.Verify(logger => logger.ErrorException(It.IsAny<string>(), It.IsAny<Exception>()); 

Очевидно, что регистратор будет предоставлен поставщику данных в настройке. Надеюсь, это будет немного более разумным.

+3

Просьба пояснить это: _Однако это означает, что я не могу проверить регистратор в своих модульных тестах. _ –

+2

За вопрос, говорящий о блочном тесте, вы не показываете ни одного фрагмента тестового кода. – manojlds

+0

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

ответ

6

Вы можете попробовать использовать шаблон фабрики.

Что происходит здесь, в вашем производственном коде, вы получаете регистратор от Factory. На этом заводе возвращается либо ваш реальный регистратор, либо поддельный регистратор, который настроен в ваших модульных тестах. Для вашего производственного кода это не имеет значения, что-то-всегда.

В ваших тестах модулей вы используете поддельный регистратор, созданный с использованием Moq. Эта подделка позволяет вам проверить, что был вызван метод интерфейса, в данном случае ILogger.Log(). Это делается с использованием метода .Verify.

попробовать что-то вроде этого:

ILogger.cs

public interface ILogger 
{ 
    void Log(string message); 
} 

LoggerFactory.cs

public static class LoggerFactory 
{ 
    public static ILogger Logger 
    { 
     get 
     { 
      return LoggerFactory._logger == null ? new Logger() : LoggerFactory._logger; 
     } 
     set 
     { 
      LoggerFactory._logger = value; 
     } 
    } 
    private static ILogger _logger = null; 
} 

DataProvider.cs

public void GetData() 
{ 
    var logger = LoggerFactory.Logger; 

    logger.Log("..."); // etc... 
} 

UnitTest.cs

private void Mock<ILogger> _mockLogger = null; 

public void Load() 
{ 
    this._mockLogger = new Mock<ILogger>(); 

    LoggerFactory.Logger = _mockLogger.Object; 
} 

public void UnitTest() 
{ 
    // test as required 

    this._mockLogger.Verify(m => m.Log(It.IsAny<string>())); 
} 
+0

+1 - возможный подход, а также использование кода в образце OP с пропущенной зависимостью в конструкторе дает гораздо меньше шансов повлиять на другие тесты, протекая логгер из этого теста (т. Е. Другой тест забывает установить логгер и начать сбой, если порядок выполнения отличается) , –

+0

Рассмотрите возможность добавления ссылки на насмешливую структуру (Moq?), Которую вы показываете. –

+0

@AlexeiLevenkov Хорошая идея, спасибо – rhughes

1

Если вам нужно проверить, что вызывается регистратор, я предлагаю использовать двойной тестовый вызов, называемый «шпион». Это не будет вести журнал, но будет отслеживать, какие методы (если они есть) были вызваны. Затем вы можете проверить, что регистратор вызывается в определенных случаях.

Вы можете сделать это, используя насмешливую структуру, чтобы создать двойной (или макет) для вас. Или вы можете создать реализацию ILogger самостоятельно. Например:

class LoggerSpy : ILogger 
{ 
    public string LogWasCalled; 

    public void Log(string message) 
    { 
     LogWasCalled = true;; 
    } 
} 

Ниже, кажется, пример насмешливых в ILogger с помощью Moq: How to Mock ILogger/ILoggerService using Moq

1

Используйте насмешливые рамки (например, MOq или RhinoMocks), чтобы проверить, что регистратор был вызван. Затем будет работать последний блок кода, который вы публикуете, где проходит регистратор через конструктор.

1

Попутный регистратор (или любая другая зависимость) в конструкторе очень стандартная практика и позволяет использовать зависимость рамку для инъекций, если это необходимо.

Я не уверен, почему вы видите прохождения регистратора в конструкторе как ограничение для модульного тестирования: у вас есть 3 компоненты, которые можно протестировать отдельно

  • контроллер (зависит от данных, обеспечивают, издеваться эту зависимость, чтобы проверить) ,
  • поставщик данных (зависит от ведения журнала и некоторых других классов, которые позволяют вам звонить в веб-службу), - издеваются над всеми зависимостями, поэтому вы знаете, когда вы вызываете вызов и не нужно звонить в веб-службу)
  • протоколирование - не знаю, на что это зависит, но должны проверяться отдельно.

Примечание:

  • использования насмешливых рамки (т.е. moq) для ваших тестов - вы будете в состоянии обеспечить любые реализации интерфейсов (включая исключение) очень легко.
  • Посмотрите, будет ли работать для вас инфраструктура инъекции зависимостей (то есть Unity). MVC4 очень хорошо подходит для него.
+0

Возможно, я делаю это неправильно, но DataProvider вызывает веб-службу, чтобы класс не тестировался точно так же, как контроллер. Вместо того, чтобы иметь вызов веб-службы в этом DataProvider, вместо этого следует использовать другой вызов интерфейса, эффективно добавляя еще один уровень, а затем включать тест интеграции? – Serberuss

+0

@Serberuss, я думаю, что DataProvider делает 2 вещи - вызывает веб-сервис и преобразует результирующие данные - в этом случае имеет смысл разделить и протестировать отдельно. Поэтому да рассмотрите другой интерфейс или какой-либо другой способ удалить необходимость вызова веб-сервиса для модульных тестов. При тестировании DataProvider в любом случае вы столкнетесь с тем же вопросом «веб-сервис с ошибкой с ошибкой» :). В конечном счете, самый низкий уровень будет простой оболочкой для некоторых рамочных методов, которые вы проверите с помощью тестов интеграции. Но обычно интересными проблемами являются разбор/обработка данных, а не получение данных ... –

+0

Большое спасибо за вашу помощь, это имеет смысл :) – Serberuss

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