ASP.net: модульное тестирование с зависимостью от прокси-класса веб-службы - PullRequest
2 голосов
/ 01 октября 2009

Допустим, у меня есть класс, подобный следующему:

public class Test{
        private RemoteDoc.Documentation docService = new RemoteDoc.Documentation();
        public Test(){}
}

Так что это затрудняет юнит-тестирование, поскольку существует зависимость от прокси-класса. Вы можете передать объект через конструктор следующим образом:

public class Test{
        private RemoteDoc.Documentation docService;
        public Test(RemoteDoc.Documentation serv)
        {
               docService = serv;
        }
}

Теперь в моих модульных тестах я могу создать экземпляр класса Test и передать макетированный объект в конструктор. Однако это решение не является идеальным, поскольку теперь другие классы должны знать о прокси-классе RemoteDoc.Documentation и иметь явные ссылки на него. Что является хорошим решением этой проблемы?

РЕДАКТИРОВАТЬ: Чтобы быть более понятным, RemoteDoc.Documentation является прокси-класс для веб-ссылки. Подумайте об этом, как если бы вы использовали API-интерфейс salesforce.com, и все, что у вас есть, - это файлы wsdl и disco.

Ответы [ 5 ]

3 голосов
/ 02 октября 2009

Ваше предлагаемое решение, которое включает передачу зависимости через конструктор, фактически идеально. Это хорошо известный паттерн Dependency Injection (DI), известный как Constructor Injection .

То, что на первый взгляд кажется слабостью, оказывается силой. Хотя верно, что каждый потребитель класса Test (в вашем примере) теперь должен предоставить некоторую реализацию прокси (я предполагаю, что прокси - это интерфейс или абстрактный базовый класс), он может предоставить любая реализация этой абстракции, а не только та, которую вы изначально имели в виду. Поздравляем: Вы только что открыли свой класс для расширяемости !

Это все еще оставляет вопрос о том, где вы на самом деле возлагаете ответственность за решение, какие зависимости находятся, куда? Вы должны сделать это в корне приложения в месте под названием Composition Root . Это объясняется более подробно в этом SO-ответе .

Вы можете использовать DI-контейнер для Auto-Wire вашей зависимости. Некоторые общие контейнеры DI:

2 голосов
/ 02 октября 2009

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

Вы можете сделать это, если код изменяется так:

public class Test
{        
     private RemoteDoc.IDocumentation docService;     

     // Constructor providing default for docService
     public Test()
     {
         docService = new RemoteDoc.Documentation();
     }   

     // Constructor for injection
     public Test(RemoteDoc.IDocumentation serv)       
     { 
          docService = serv;        
     }
}

Затем вы создаете фиктивный объект документации, используя макет фреймворка, такой как:

... и передать его в конструктор Test (RemoteDoc.IDocumentation serv).

Поскольку RemoteDoc.Documentation является конкретным классом, его можно наследовать от RemoteDoc.IDocumentation с использованием частичного класса:

namespace RemoteDoc
{
    public interface IDocumentation
    {
        // public functions you want to mock go here
        string GetDocumentation();
    }

    public partial class Documentation : IDocumentation {}
}
0 голосов
/ 02 октября 2009

Как насчет этого подхода? Вам нужно будет написать больше кода для поддержки функциональности прокси-класса. Но это даст вам гибкость для модульного тестирования.

public interface IDocumentation
{
    // Add whatever functionality you need from RemoteDoc.Documentation
}

public class RemoteDocumnetation : IDocumentation
{
    private RemoteDoc.Documentation docService = new RemoteDoc.Documentation();

    // Implements IDocumentation 
}

public class Test{
        private IDocumentation doc;
        public Test(IDocumentation serv)
        {
               doc= serv;
        }
}
0 голосов
/ 02 октября 2009

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

0 голосов
/ 02 октября 2009

Я второй подход Марка. Для полноты другой вариант выглядит следующим образом:

public class Test
{        
     private RemoteDoc.Documentation docService;     

     // Constructor providing default for docService
     public Test()
     {
         docService = new RemoteDoc.Documentation();
     }   

     // Constructor for injection
     public Test(RemoteDoc.Documentation serv)       
     { 
          docService = serv;        
     }
}

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

Я использовал оба подхода в прошлом. При разработке нетривиального программного обеспечения подход с использованием DI-контейнера обычно является лучшим способом.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...