Как внедрить клиентские зависимости wcf в ViewModel и сделать его тестируемым?

TL; DR: Каков хороший и проверяемый способ реализовать зависимость между ViewModels и службами WCF в клиенте MVVM?

Прочтите оставшуюся часть вопроса, чтобы узнать больше о проблемах, с которыми я столкнулся при попытке сделать это:


Я работаю над клиентом silverlight, который подключается к службе wcf, и я хочу написать модульные тесты для клиента. Итак, я ищу хорошее решение для использования клиентов wcf в моих ViewModels и тестирования этого взаимодействия. До сих пор я нашел два решения:

Решение 1. Вот как я реализовал это до сих пор:

public class ViewModelExample
{
    public ViewModelExample(IServiceClient client)
    {    
            client.DoWorkCompleted += .. 
            client.DoWorkAsync();
    }
}

//This is how the interface looks like
public interface IServiceClient
{
    event EventHandler<AsyncCompletedEventArgs> DoWorkCompleted;
    void DoWorkAsync();
}

//I was able to put the interface on the generated clients because they are partial classes, like this:
public partial class GeneratedServiceClient : IServiceClient
{

}

Хорошая сторона: сравнительно легко высмеять

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

Решение 2. На основе этого ответа Срок службы клиента службы WCF.

public class ViewModelExample
{
    public ViewModelExample(IServiceFactory factory)
    {
        var client = factory.CreateClient();
        client.DoWorkCompleted += ...
        client.DoWorkAsync();
    }
}

Хорошая сторона: каждый запрос поступает от другого клиента, поэтому больше нет проблем с сопоставлением запросов с ответами.

Плохая часть: труднее тестировать. Мне каждый раз приходилось писать макеты как для фабрики, так и для клиента wcf. Я не хотел бы этого делать, так как у меня уже 200 тестов ... :(

У меня вопрос, как вы, ребята, это делаете? Как ваши ViewModels взаимодействуют со службами wcf, куда вы вводите зависимость и как вы тестируете это взаимодействие? Я чувствую, что что-то упускаю ..


person stralsi    schedule 20.06.2012    source источник


Ответы (3)


Попробуйте внедрить Func<IServiceClient> в вашу виртуальную машину вместо экземпляра клиента; у вас будет внедрена «фабрика уровня языка» вместо того, чтобы создавать для этого класс. В фабричном методе вы можете создать экземпляр своего клиента, как хотите (например, каждый доступ может создать новый экземпляр для этого).

Обратной стороной является то, что вам все равно придется по большей части трогать свои тесты, но я предполагаю, что это будет меньше работы:

public ViewModelExample(Func<IServiceClient> factoryMethod)
{
    var client = factoryMethod();
    client.DoWorkCompleted += ...
    client.DoWorkAsync();
}
person Stefan Szasz    schedule 20.06.2012

У службы WCF должны быть собственные тесты, подтверждающие ее функциональность.

Затем вы должны издеваться над этой службой WCF и писать модульные тесты для своих потребителей.

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

person ChrisBint    schedule 20.06.2012
comment
Спасибо, Крис, но это именно то, о чем я спрашивал, как писать сервисы и как писать для них моки? Как ты это делаешь? - person stralsi; 20.06.2012

Вы случайно используете контейнер IoC? Если бы это было так, эта проблема была бы полностью решена с помощью контейнера (вы просто зарегистрируете IService зависимость, которая будет создаваться как совершенно новая при каждом запросе).

Если это не так, тогда

Мне каждый раз приходилось писать макеты как для фабрики, так и для клиента wcf

как вы справляетесь с такого рода "проблемами". Стоимость относительно невелика, возможно, 2-3 дополнительных строки кода на тест (все, что вам нужно сделать, это настроить фабричный макет, чтобы вернуть сервисный макет, который вам нужен в любом случае).

person k.m    schedule 20.06.2012
comment
В настоящее время я не использую контейнер IoC, но, на мой взгляд, это не изменит ситуацию, поскольку зависимость IService находится в конструкторе, и мне понадобится один для каждого вызова. .. но вы дали мне другую идею, как вы думаете, возможно ли использовать контейнер IoC вместо фабрики? Разрешать IService перед каждым звонком? - person stralsi; 20.06.2012
comment
@sssilviu: почему вы думаете, что не сможете предоставить клиентский экземпляр для каждой виртуальной машины? Большинство современных контейнеров позволяют значительно контролировать создание / время жизни объектов. Всякий раз, когда создается новый экземпляр виртуальной машины (при условии, что он создается через контейнер), он может получать новый экземпляр IService, таким образом заставляя контейнер IoC работать точно как фабрика / фабрика функций. Это просто вопрос конфигурации / регистрации. - person k.m; 20.06.2012
comment
В настоящее время у меня есть клиентские экземпляры на каждую виртуальную машину. Мне они нужны для каждого обращения в службу поддержки. - person stralsi; 20.06.2012
comment
Проблема с клиентскими экземплярами на каждую виртуальную машину заключается в том, что виртуальная машина делает 10 запросов по одному и тому же методу от одного и того же клиента, когда возвращаются ответы, я не знаю, какой ответ принадлежит какому запросу. - person stralsi; 20.06.2012
comment
@sssilviu: Понятно. Затем вы должны использовать factory для получения нового экземпляра клиента с той же виртуальной машины. Обратите внимание, что некоторые контейнеры (например, Autofac позволяет это сделать) разрешают зависимости для Func<IService>, даже если вы просто зарегистрируете IService. Упаковка Func<T> выполняется контейнером автоматически. - person k.m; 20.06.2012