Мы пробовали модульные тесты записи на класс рабочего, записанный в C#, который дразнит третье лицо API (базирующийся COM) использующий moq для динамичного создания фиктивных объектов. NUnit является нашей платформой поблочного тестирования.
Этот сторонний компонент реализует несколько интерфейсов, но также и должен перезвонить в наши события использования класса рабочего. Наш план состоял в том, чтобы моделировать события, которые этот сторонний компонент может сгенерировать, и тест что наш класс рабочего, управляемый как ожидалось.
К сожалению, мы столкнулись с проблемой, в которой moq кажется не могущим дразнить и сгенерировать события, которые определяются внешне. К сожалению, я не могу предоставить код для точной третьей стороны API, который мы используем, но мы воссоздали проблему с помощью MS Word API и также показали, как тесты работают когда при использовании локально определенного интерфейса:
using Microsoft.Office.Interop.Word;
using Moq;
using NUnit.Framework;
using SeparateNamespace;
namespace SeparateNamespace
{
public interface LocalInterface_Event
{
event ApplicationEvents4_WindowActivateEventHandler WindowActivate;
}
}
namespace TestInteropInterfaces
{
[TestFixture]
public class Test
{
[Test]
public void InteropExample()
{
// from interop
Mock<ApplicationEvents4_Event> mockApp = new Mock<ApplicationEvents4_Event>();
// identical code from here on...
bool isDelegateCalled = false;
mockApp.Object.WindowActivate += delegate { isDelegateCalled = true; };
mockApp.Raise(x => x.WindowActivate += null, null, null);
Assert.True(isDelegateCalled);
}
[Test]
public void LocalExample()
{
// from local interface
Mock<LocalInterface_Event> mockApp = new Mock<LocalInterface_Event>();
// identical code from here on...
bool isDelegateCalled = false;
mockApp.Object.WindowActivate += delegate { isDelegateCalled = true; };
mockApp.Raise(x => x.WindowActivate += null, null, null);
Assert.True(isDelegateCalled);
}
}
}
Кто-либо мог объяснить, почему генерирование событий для локально определенного интерфейса работает, но не тот, импортированный из третьей стороны API (в этом случае Word)?
У меня есть чувство, что это относится к факту, мы говорим с COM-объектом (через interop блок), но не уверено, как работать вокруг проблемы.
Moq «перехватывает» события, обнаруживая вызовы внутренних методов события. Эти методы называются add_
+ имя события и являются «особыми» в том смысле, что они нестандартные методы C #.События чем-то похожи на свойства ( get
/ set
) и могут быть определены следующим образом:
event EventHandler MyEvent
{
add { /* add event code */ };
remove { /* remove event code */ };
}
Если вышеупомянутое событие было определено на интерфейсе, который должен быть Moq'd, следующий код будет использоваться для вызова этого события:
var mock = new Mock<IInterfaceWithEvent>;
mock.Raise(e => e.MyEvent += null);
Поскольку в C # невозможно напрямую ссылаться на события, Moq перехватывает все вызовы методов в Mock и проверяет, должен ли этот вызов добавить обработчик событий (в приведенном выше случае, добавлен нулевой обработчик). В таком случае ссылка может быть получена косвенно как «цель» метода.
Метод обработчика событий обнаруживается Moq с использованием отражения как метода, начинающегося с имени add_
и с установленным флагом IsSpecialName
. Эта дополнительная проверка предназначена для фильтрации вызовов методов, не связанных с событиями, но с именем, начинающимся с add_
.
В этом примере перехваченный метод будет называться add_MyEvent
и иметь установленный флаг IsSpecialName
.
Однако кажется, что это не совсем верно для интерфейсов, определенных в взаимодействиях, поскольку, хотя имя метода обработчика событий начинается с add_
, оно не имеет Установлен флаг IsSpecialName
. Это может быть связано с тем, что события маршалируются через код нижнего уровня в функции (COM), а не являются истинными «особыми» событиями C #.
Это можно показать (следуя вашему примеру) с помощью следующего теста NUnit:
MethodInfo interopMethod = typeof(ApplicationEvents4_Event).GetMethod("add_WindowActivate");
MethodInfo localMethod = typeof(LocalInterface_Event).GetMethod("add_WindowActivate");
Assert.IsTrue(interopMethod.IsSpecialName);
Assert.IsTrue(localMethod.IsSpecialName);
Кроме того, нельзя создать интерфейс, который наследует интерфейс from interop для решения проблемы, поскольку он также унаследует упорядоченное add
/ удалить
методы.
Об этой проблеме было сообщено в системе отслеживания проблем Moq здесь: http://code.google.com/p/moq/issues/detail?id=226
Обновление:
Пока это не будет решено разработчиками Moq, единственным обходным решением может быть использование отражения для изменения интерфейса, которое, похоже, противоречит цели использования Moq. К сожалению, для этого случая может быть лучше просто «накатать свой собственный» Moq.
Эта проблема исправлена в Moq 4.0 (от августа 2011 г.).
Можете ли вы переопределить интерфейс COM с 3-го вечеринки и использовать его с MOQ.
Кажется, что ваше намерение состоит в том, чтобы MOQ прочь Внешняя зависимость и MOQ красиво не играют с сборкой Cominterop, вы должны быть в состоянии открыть отражатель и вытащить какие-либо определения интерфейса, которые вы хотите из сборки Interop. Ваши модульные тесты