Мокинг запечатанных классов может быть настоящей болью. В настоящее время я предпочитаю Шаблон адаптера, чтобы справиться с этим, но что-то в этом остается странным.
Итак, как лучше всего высмеивать закрытые классы?
Ответы на Java более чем приветствуются. Фактически, я ожидал, что сообщество Java занимается этим дольше и может многое предложить.
Но вот некоторые из мнений .NET:





Есть ли способ реализовать запечатанный класс из интерфейса ... и вместо этого издеваться над интерфейсом?
Что-то во мне кажется, что закрытые классы - это в первую очередь неправильно, но это только я :)
Я думаю, что закрытые классы - это здорово ... проблема в тестировании. Воняет, что мы должны полностью изменить наш дизайн из-за ограничений в тестировании проектов .NET. Во многих случаях Д. это просто способ обойти тот факт, что статические классы / методы сложно тестировать.
Мое общее эмпирическое правило состоит в том, что объекты, которые мне нужно имитировать, также должны иметь общий интерфейс. Я думаю, что это правильно с точки зрения дизайна и значительно упрощает тестирование (и обычно это то, что вы получаете, если выполняете TDD). Подробнее об этом можно прочитать в блоге Google Testing последний пост (см. Пункт 9).
Кроме того, последние 4 года я работал в основном на Java, и могу сказать, что могу сосчитать по одной руке, сколько раз я создавал финальный (запечатанный) класс. Еще одно правило: у меня всегда должна быть веская причина для запечатывания класса, а не для запечатывания по умолчанию.
Я бы сказал, что у вас должна быть веская причина нет, чтобы запечатать класс. Оставление класса открытым означает, что вам нужно подумать о том, как он будет использоваться наследниками, что открывает шлюз решений по всему коду в классе (виртуальность, защищенные свойства или частные переменные-члены и т. д.). спроектируйте класс для наследования. Вы не должны иметь возможность расширять класс только ради расширения; вывод должен означать что-то конкретное для моделируемой проблемы. В противном случае отдавайте предпочтение композиции.
Брайан, а как бы вы тогда издевались над закрытым классом? Я согласен с вами с теоретической точки зрения, но если вы пишете что-то, зависящее от вашего запечатанного класса, ваши тесты также зависят от запечатанного класса.
@abyx: К сожалению, не всегда разработчик выбирает, является ли класс запечатанным или нет. Возьмем, например, System.Web.HttpServerUtility в ASP.NET ...
@BryanWatts Я категорически не согласен с этим. Запечатывание класса просто лишает кодировщиков возможности наследовать класс. Они могут найти его полезным способом, о котором вы даже не догадывались. Вам не нужно беспокоиться о том, что они как-то напортачят; это (вероятно) не ваша ответственность. Я бы вообще отказался от ключевого слова sealed.
@Jez: Если бы я не рассматривал расширение при написании класса, по какой причине ему доверять?
Полностью согласен с @Jez. Приятно видеть, что официальное руководство разделяет то же мнение: «НЕ запечатывайте классы без уважительной причины. Запечатывание класса из-за того, что вы не можете придумать сценарий расширяемости, не является хорошей причиной». (docs.microsoft.com/en-us/dotnet/standard/design-guidelines/…) Чтобы защитить методы от неправильного использования подклассом, просто оставьте их закрытыми. Разве этого не достаточно?
Для .NET вы можете использовать что-то вроде Тип: Мок, который использует API профилирования и позволяет подключаться к вызовам практически ко всему.
+1. Используйте правильные инструменты. Не позволяйте инструментам диктовать вам, как вы должны что-то делать. API-интерфейсы предназначены для людей, а не для инструментов - создавайте их как таковые. Используйте DI, интерфейсы и полную развязку там, где это имеет смысл, а не только потому, что вам это нужно для инструментов тестирования.
Павел - проблема в том, что «Правильный» путь в .NET обычно ведет вас по пути «Использовать TypeMock для тестирования». Путь, по которому доступен только один инструмент тестирования, тоже не кажется отличным.
Некоторым людям может не понравиться платить 80 долларов в месяц за тестовый фреймворк.
Я думаю, что Moles позволяет вам работать с закрытыми классами - и если у вас есть подписка MSDN, это бесплатно.
AFAIK одна из основных проблем этих фреймворков, которые могут имитировать запечатанные классы, статику и т. д., - это производительность. Лично я предпочитаю создавать интерфейс и использовать его в своем производственном коде с наследственным прокси-объектом, вводимым во время производства, и имитацией, вводимой во время тестирования.
Альтернативой является Telerik JustMock. Вы можете использовать коммерческую версию для тестирования устаревшего кода, а затем перейти к бесплатной версии после того, как будут реализованы интерфейсы и внедрение зависимостей.
Проблема с TypeMock в том, что он оправдывает плохой дизайн. Теперь я знаю, что часто скрывается плохой дизайн чей-то еще, но включение его в процесс разработки может очень легко привести к разрешению ваших собственных плохих проектов.
Я думаю, что если вы собираетесь использовать фиктивный фреймворк, вам следует использовать традиционный (например, Moq) и создать слой изоляции вокруг немоделируемого объекта, а вместо этого имитировать слой изоляции.
Не накладывать интерфейсы на все, что находится на виду, только потому, что они вам нужны из-за недостатков ваших инструментов тестирования, - это неплохой дизайн. На самом деле, как раз наоборот - это дизайн здравомыслящий, который касается дизайна, а не соответствия вашим инструментам. Клянусь, иногда мне кажется, что TDD, как это часто догматически практикуется, действительно следует называть «инструментальным дизайном». Также см. weblogs.asp.net/rosherove/archive/2008/01/17/…
Павел - есть ли у вас другой инструмент, кроме TypeMock, который может обеспечить этот тип тестирования? Тестирование классов, построенных с разумным дизайном (например, с использованием статических методов, новых вызовов в коде, ограничения интерфейсов там, где требуется несколько реализаций и т. д.), Часто практически невозможно протестировать.
Я согласен с Брэдом здесь. Создание фиктивного объекта подразумевает, что вы тестируете публичное поведение этого типа. То есть вы конкретно говорите: «Мне нужно, чтобы общедоступный API этого объекта вел себя определенным образом». Это поведение независимый, однако упомянутый API реализован. Это означает, что у вас есть уже определенная (хотя и неявная) абстракция, которая должна вести себя определенным образом. В системе статических типов это должно быть явным путем создания абстрактного типа.
Обычно я выбираю путь создания интерфейса и класса адаптера / прокси, чтобы облегчить имитацию запечатанного типа. Тем не менее, я также экспериментировал с пропуском создания интерфейса и созданием незапечатанного типа прокси с помощью виртуальных методов. Это хорошо сработало, когда прокси действительно является естественным базовым классом, который инкапсулирует и использует часть запечатанного класса.
Имея дело с кодом, который требовал такой адаптации, я устал выполнять одни и те же действия для создания интерфейса и типа прокси, поэтому я реализовал библиотеку для автоматизации задачи.
Код несколько сложнее, чем пример, приведенный в статье, на которую вы ссылаетесь, поскольку он создает сборку (вместо исходного кода), позволяет выполнять генерацию кода для любого типа и не требует такой большой настройки.
Для получения дополнительной информации обратитесь к эта страница.
Я почти всегда избегаю зависимости от внешних классов глубоко в моем коде. Вместо этого я бы предпочел использовать адаптер / мост, чтобы поговорить с ними. Таким образом, я имею дело со своей семантикой, и боль перевода изолирована в одном классе.
Это также упрощает переключение моих зависимостей в долгосрочной перспективе.
Вполне разумно издеваться над запечатанным классом, потому что многие классы фреймворка запечатаны.
В моем случае я пытаюсь издеваться над классом MessageQueue .Net, чтобы я мог TDD свою изящную логику обработки исключений.
Если у кого-то есть идеи о том, как преодолеть ошибку Moq, касающуюся «Недопустимой настройки на непереопределяемом члене», дайте мне знать.
код:
[TestMethod]
public void Test()
{
Queue<Message> messages = new Queue<Message>();
Action<Message> sendDelegate = msg => messages.Enqueue(msg);
Func<TimeSpan, MessageQueueTransaction, Message> receiveDelegate =
(v1, v2) =>
{
throw new Exception("Test Exception to simulate a failed queue read.");
};
MessageQueue mockQueue = QueueMonitorHelper.MockQueue(sendDelegate, receiveDelegate).Object;
}
public static Mock<MessageQueue> MockQueue
(Action<Message> sendDelegate, Func<TimeSpan, MessageQueueTransaction, Message> receiveDelegate)
{
Mock<MessageQueue> mockQueue = new Mock<MessageQueue>(MockBehavior.Strict);
Expression<Action<MessageQueue>> sendMock = (msmq) => msmq.Send(It.IsAny<Message>()); //message => messages.Enqueue(message);
mockQueue.Setup(sendMock).Callback<Message>(sendDelegate);
Expression<Func<MessageQueue, Message>> receiveMock = (msmq) => msmq.Receive(It.IsAny<TimeSpan>(), It.IsAny<MessageQueueTransaction>());
mockQueue.Setup(receiveMock).Returns<TimeSpan, MessageQueueTransaction>(receiveDelegate);
return mockQueue;
}
Я нашел решение и разместил здесь: dotnetmonkey.net/post/Hard-to-Moq.aspx
жаль, что страница мертва
спасибо за решение! страница все еще мертва
Я считаю, что Родинки из Microsoft Research позволяет вам это делать. Со страницы Родинок:
Moles may be used to detour any .NET method, including non-virtual/static methods in sealed types.
Обновлено: в предстоящем выпуске VS 11 есть новый фреймворк под названием «Подделки», предназначенный для замены Moles:
The Fakes Framework in Visual Studio 11 is the next generation of Moles & Stubs, and will eventually replace it. Fakes is different from Moles, however, so moving from Moles to Fakes will require some modifications to your code. A guide for this migration will be available at a later date.
Requirements: Visual Studio 11 Ultimate, .NET 4.5
Хотя в настоящее время он доступен только в бета-версии, я думаю, что стоит помнить о функции прокладка нового Подделки рамки (часть бета-версии Visual Studio 11).
Shim types provide a mechanism to detour any .NET method to a user defined delegate. Shim types are code-generated by the Fakes generator, and they use delegates, which we call shim types, to specify the new method implementations. Under the hood, shim types use callbacks that were injected at runtime in the method MSIL bodies.
Лично я пытался использовать это для имитации методов в закрытых классах фреймворка, таких как DrawingContext.
Я недавно столкнулся с этой проблемой, и после чтения / поиска в Интернете кажется, что нет простого способа, кроме использования другого инструмента, как упоминалось выше. Или грубо поступать так, как я:
System.Runtime.Serialization.FormatterServices.GetUninitializedObject (instanceType);
Присвойте значения вашим свойствам / полям через отражение
Это единственное решение, которое имеет смысл и работает для имитации внутреннего материала Microsoft без добавления каких-либо дополнительных библиотек, таких как Fakes - спасибо!
Не смейся! Вместо этого используйте композицию.