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

Ran*_*age 16 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);
Run Code Online (Sandbox Code Playgroud)

问题是,由于TelemetryClient有2个构造函数,SI抱怨并且验证失败.我找到了一篇文章,展示了如何覆盖容器的构造函数解析行为,但这似乎相当复杂.首先,我想要备份并提出这个问题:

如果我没有使TelemetryClient成为一个注入的依赖项(只是在类中创建一个新的),那么在每次运行单元测试时是否会将遥测发送到Azure,从而产生大量错误数据?或者Application Insights是否足够聪明,知道它在单元测试中运行,而不是发送数据?

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

谢谢

Ste*_*ven 21

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

TelemetryClient是一种框架类型,框架类型不应由容器自动连接.

我找到了一篇文章,展示了如何覆盖容器的构造函数解析行为,但这似乎相当复杂.

是的,这种复杂性是刻意的,因为我们希望阻止人们创建具有多个构造函数的组件,因为这是一种反模式.

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

container.Register<TelemetryClient>(() => 
    new TelemetryClient(/*whatever values you need*/),
    requestOrTransientLifestyle);
Run Code Online (Sandbox Code Playgroud)

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

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

你应该做的是定义一个(或者甚至多个)抽象TelemetryClient,特别是为你的应用量身定制.因此,不要试图TelemetryClient用它可能的100种方法来模仿API,而只是在控制器实际使用的接口上定义方法,并使它们尽可能简单,这样你就可以使控制器的代码更简单 - 而且 - 你的单元测试更简单.

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

container.RegisterSingleton<ITelemetryLogger>(
    new TelemetryClientAdapter(new TelemetryClient(...)));
Run Code Online (Sandbox Code Playgroud)

在这里,我假设它TelemetryClient是线程安全的,可以作为单例工作.否则,您可以执行以下操作:

container.RegisterSingleton<ITelemetryLogger>(
    new TelemetryClientAdapter(() => new TelemetryClient(...)));
Run Code Online (Sandbox Code Playgroud)

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

container.RegisterSingleton<ITelemetryLogger>(new TelemetryClientAdapter());
Run Code Online (Sandbox Code Playgroud)

  • 谢谢你这个详细的答案.正如史蒂夫和@qujck建议的那样,我的主要问题是用一个简单的单线程来回答用我选择的构造函数注册遥测客户端(尽管史蒂夫的答案有一个拼写错误 - 容器.重复.).在发布这个问题后,我得出了与单元测试问题完全相同的解决方案 - 将TelemetryClient包装在我自己设计的一个简单类中,公开我需要的一个方法,为它创建一个接口,等等.然后在单元测试中,这个简单的包装类是用Moq模拟的. (2认同)

Jam*_*ood 14

Application Insights有一个通过模拟进行单元测试的示例.TelemetryClientTelemetryChannel

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);
Run Code Online (Sandbox Code Playgroud)


Jas*_*ell 7

我有很多成功的使用乔希Rostad的文章写我的模拟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()
    {

    }
}    
Run Code Online (Sandbox Code Playgroud)

然后在我的测试中,一个本地方法来启动模拟:

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;
}
Run Code Online (Sandbox Code Playgroud)

最后,运行测试!

[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);
}
Run Code Online (Sandbox Code Playgroud)


Dal*_*oft 5

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

appInsightsSettings.json

    "ApplicationInsights": {
    "Endpoint": "http://localhost:8888/v2/track"
}
Run Code Online (Sandbox Code Playgroud)

如何在 ASP.NET Core 中设置“TestServer” http://josephwoodward.co.uk/2016/07/integration-testing-asp-net-core-middleware


hum*_*ice 5

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

TelemetryConfiguration.Active.DisableTelemetry = true;