Использование анализа приложений с помощью модульных тестов?

У меня есть веб-приложение 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;