Использование анализа приложений с помощью модульных тестов?
У меня есть веб-приложение MVC, и я использую Simple Injector для DI. Почти весь мой код покрыт юнит-тестами. Однако теперь, когда я добавил несколько вызовов телеметрии в некоторые контроллеры, у меня возникли проблемы с настройкой зависимостей.
Телеметрические вызовы предназначены для отправки метрик в службу Application Insights, расположенную в Microsoft Azure. Приложение не работает в Azure, просто сервер с ISS. Портал ИИ расскажет вам все о вашем приложении, включая любые пользовательские события, которые вы отправляете с помощью библиотеки телеметрии. В результате для контроллера требуется экземпляр Microsoft.ApplicationInsights.TelemetryClient, который не имеет интерфейса и представляет собой закрытый класс с двумя конструкторами. Я попытался зарегистрировать это так (гибридный образ жизни не связан с этим вопросом, я просто включил его для полноты):
// hybrid lifestyle that gives precedence to web api request scope
var requestOrTransientLifestyle = Lifestyle.CreateHybrid(
() => HttpContext.Current != null,
new WebRequestLifestyle(),
Lifestyle.Transient);
container.Register<TelemetryClient>(requestOrTransientLifestyle);
Проблема в том, что, поскольку TelemetryClient имеет 2 конструктора, SI жалуется и не проходит проверку. Я нашел сообщение, показывающее, как переопределить поведение разрешения конструктора контейнера, но это кажется довольно сложным. Сначала я хотел сделать резервную копию и задать этот вопрос:
Если я не сделаю TelemetryClient внедренной зависимостью (просто создаю новую в классе), будет ли эта телеметрия отправляться в Azure при каждом запуске модульного теста, создавая много ложных данных? Или Application Insights достаточно умен, чтобы знать, что он выполняется в модульном тесте, а не отправлять данные?
Любые "идеи" по этому вопросу будут высоко оценены!
Спасибо
Ответы
Ответ 1
Microsoft.ApplicationInsights.TelemetryClient, который не имеет интерфейса и является закрытым классом, с 2 конструкторами.
Этот TelemetryClient
- это тип структуры и типы фреймов не должны быть автоматически подключены вашим контейнером.
Я нашел сообщение, показывающее, как переопределить поведение разрешения конструктора контейнера, но это кажется довольно сложным.
Да, эта сложность является преднамеренной, потому что мы хотим препятствовать людям создавать компоненты с несколькими конструкторами, потому что это анти-шаблон.
Вместо использования автоматической проводки вы можете, как уже указывал @qujck, просто сделать следующую регистрацию:
container.Register<TelemetryClient>(() =>
new TelemetryClient(/*whatever values you need*/),
requestOrTransientLifestyle);
Или это приложение Insights достаточно умное, чтобы знать, что оно работает в unit test, а не отправлять данные?
Очень маловероятно. Если вы хотите протестировать класс, который зависит от этого TelemetryClient
, вместо этого лучше использовать поддельную реализацию, чтобы ваш unit test не стал хрупким, медленным или не загрязнял ваши данные Insight. Но даже если тестирование не вызывает беспокойства, в соответствии с Принципом инверсии зависимостей вы должны зависеть от (1) абстракций, которые (2) определяются вашим собственным приложением. Вы не используете обе точки при использовании TelemetryClient
.
Вместо этого вы должны определить одну (или, возможно, даже несколько) абстракции над TelemetryClient
, которые специально разработаны для вашего приложения. Поэтому не пытайтесь имитировать API TelemetryClient
с его возможными 100 методами, но только определите методы интерфейса, которые фактически использует ваш контроллер, и сделайте их как можно более простыми, чтобы вы могли сделать код контроллера более простым - ваш блок проще.
После того, как вы определили хорошую абстракцию, вы можете создать реализацию адаптера, которая использует TelemetryClient
внутренне. Я хочу, чтобы вы зарегистрировали этот адаптер следующим образом:
container.RegisterSingleton<ITelemetryLogger>(
new TelemetryClientAdapter(new TelemetryClient(...)));
Здесь я предполагаю, что TelemetryClient
является потокобезопасным и может работать как одноэлементный. В противном случае вы можете сделать что-то вроде этого:
container.RegisterSingleton<ITelemetryLogger>(
new TelemetryClientAdapter(() => new TelemetryClient(...)));
Здесь адаптер по-прежнему один, но имеет делегат, который позволяет создавать TelemetryClient
. Другой вариант - позволить адаптеру создать (и, возможно, удалить) TelemetryClient
внутренне. Возможно, это упростит регистрацию:
container.RegisterSingleton<ITelemetryLogger>(new TelemetryClientAdapter());
Ответ 2
В Application Insights есть пример модульного тестирования TelemetryClient
с помощью насмешки TelemetryChannel
.
TelemetryChannel
реализует ITelemetryChannel
, поэтому довольно легко издеваться и внедрять. В этом примере вы можете регистрировать сообщения, а затем собирать их позже из Items
для утверждений.
public class MockTelemetryChannel : ITelemetryChannel
{
public IList<ITelemetry> Items
{
get;
private set;
}
...
public void Send(ITelemetry item)
{
Items.Add(item);
}
}
...
MockTelemetryChannel = new MockTelemetryChannel();
TelemetryConfiguration configuration = new TelemetryConfiguration
{
TelemetryChannel = MockTelemetryChannel,
InstrumentationKey = Guid.NewGuid().ToString()
};
configuration.TelemetryInitializers.Add(new OperationCorrelationTelemetryInitializer());
TelemetryClient telemetryClient = new TelemetryClient(configuration);
container.Register<TelemetryClient>(telemetryClient);
Ответ 3
У меня был большой успех с использованием статьи Джоша Ростада article для написания моего ложного TelemetryChannel и внедрения его в мои тесты. Здесь макет объекта:
public class MockTelemetryChannel : ITelemetryChannel
{
public ConcurrentBag<ITelemetry> SentTelemtries = new ConcurrentBag<ITelemetry>();
public bool IsFlushed { get; private set; }
public bool? DeveloperMode { get; set; }
public string EndpointAddress { get; set; }
public void Send(ITelemetry item)
{
this.SentTelemtries.Add(item);
}
public void Flush()
{
this.IsFlushed = true;
}
public void Dispose()
{
}
}
А потом в моих тестах, локальный метод раскрутки макета:
private TelemetryClient InitializeMockTelemetryChannel()
{
// Application Insights TelemetryClient does not have an interface (and is sealed)
// Spin -up our own homebrew mock object
MockTelemetryChannel mockTelemetryChannel = new MockTelemetryChannel();
TelemetryConfiguration mockTelemetryConfig = new TelemetryConfiguration
{
TelemetryChannel = mockTelemetryChannel,
InstrumentationKey = Guid.NewGuid().ToString(),
};
TelemetryClient mockTelemetryClient = new TelemetryClient(mockTelemetryConfig);
return mockTelemetryClient;
}
Наконец, запустите тесты!
[TestMethod]
public void TestWidgetDoSomething()
{
//arrange
TelemetryClient mockTelemetryClient = this.InitializeMockTelemetryChannel();
MyWidget widget = new MyWidget(mockTelemetryClient);
//act
var result = widget.DoSomething();
//assert
Assert.IsTrue(result != null);
Assert.IsTrue(result.IsSuccess);
}
Ответ 4
Если вы не хотите идти по пути абстракции/обертки. В ваших тестах вы можете просто направить конечную точку AppInsights на макетный легкий HTTP-сервер (который тривиально в ядре ASP.NET).
appInsightsSettings.json
"ApplicationInsights": {
"Endpoint": "http://localhost:8888/v2/track"
}
Как настроить "TestServer" в ядре ASP.NET http://josephwoodward.co.uk/2016/07/integration-testing-asp-net-core-middleware
Ответ 5
Еще один вариант без перехода к абстракции - отключить телеметрию перед выполнением тестов:
TelemetryConfiguration.Active.DisableTelemetry = true;