将Application Insights与单元测试结合使用?

时间:2015-11-19 22:17:57

标签: c# unit-testing azure simple-injector azure-application-insights

我有一个MVC网络应用程序,我正在使用Simple Injector进行DI。我的几乎所有代码都包含在单元测试中。但是,现在我已经在某些控制器中添加了一些遥测调用,我在设置依赖项时遇到了麻烦。

遥测调用用于将指标发送到Microsoft Azure托管的Application Insights服务。该应用程序未在Azure中运行,只是一个带有ISS的服务器。 AI门户网站会告诉您有关应用程序的各种信息,包括使用遥测库发送的任何自定义事件。因此,控制器需要一个Microsoft.ApplicationInsights.TelemetryClient实例,该实例没有接口,并且是一个带有2个构造函数的密封类。我尝试注册它(混合生活方式与这个问题无关,我只是为了完整性而包含它):

      // 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是否足够聪明,知道它在单元测试中运行,而不是发送数据?

对此问题的任何“见解”将不胜感激!

由于

5 个答案:

答案 0 :(得分:18)

  

Microsoft.ApplicationInsights.TelemetryClient,没有接口,是一个密封类,有2个构造函数。

TelemetryClient是一种框架类型,framework types should not be auto-wired by your container

  

我发现了一篇文章,展示了如何覆盖容器的构造函数解析行为,但这看起来非常复杂。

是的,这种复杂性是刻意的,因为我们希望阻止人们创建具有多个构造函数的组件,因为这是an anti-pattern

正如@qujck已经指出的那样,您可以简单地进行以下注册,而不是使用自动布线:

container.Register<TelemetryClient>(() => 
    new TelemetryClient(/*whatever values you need*/),
    requestOrTransientLifestyle);
  

或者Application Insights是否足够聪明,知道它是在单元测试中运行,而不是发送数据?

非常不可能。如果要测试依赖于此TelemetryClient的类,最好使用虚假实现,以防止单元测试变得脆弱,缓慢或污染您的Insight数据。但即使测试不是问题,根据Dependency Inversion Principle,您应该依赖于(1)由您自己的应用程序定义的(2)抽象。使用TelemetryClient时,您都失败了两个点。

您应该做的是在TelemetryClient上定义一个(或者甚至多个)抽象,这些抽象是针对您的应用专门定制的 。因此,不要尝试使用可能的100种方法来模仿TelemetryClient的API,而只是在控制器实际使用的接口上定义方法,并使它们尽可能简单,以便你可以使控制器的代码更简单 - 并且你的单元测试更简单。

在定义好的抽象之后,您可以创建在内部使用TelemetryClient的适配器实现。我想象你注册这个适配器如下:

container.RegisterSingleton<ITelemetryLogger>(
    new TelemetryClientAdapter(new TelemetryClient(...)));

这里我假设TelemetryClient是线程安全的,可以作为单例工作。否则,你可以这样做:

container.RegisterSingleton<ITelemetryLogger>(
    new TelemetryClientAdapter(() => new TelemetryClient(...)));

此处适配器仍然是单例,但提供了一个允许创建TelemetryClient的委托。另一个选择是让适配器在内部创建(并可能配置)TelemetryClient。这可能会使注册更简单:

container.RegisterSingleton<ITelemetryLogger>(new TelemetryClientAdapter());

答案 1 :(得分:12)

Application Insights有example个单元通过模拟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);

答案 2 :(得分:6)

使用Josh Rostad的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 doesn't 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);
}

答案 3 :(得分:3)

如果您不想沿着抽象/包装路径走下去。在您的测试中,您可以简单地将AppInsights端点指向模拟轻量级http服务器(在ASP.NET Core中很简单)。

appInsightsSettings.json

    "ApplicationInsights": {
    "Endpoint": "http://localhost:8888/v2/track"
}

如何在ASP.NET Core http://josephwoodward.co.uk/2016/07/integration-testing-asp-net-core-middleware

中设置“TestServer”

答案 4 :(得分:1)

没有采用抽象路线的另一个选择是在运行测试之前禁用遥测:

TelemetryConfiguration.Active.DisableTelemetry = true;