将Application Insights与unit testing结合使用?

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

遥测调用用于将指标发送到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(requestOrTransientLifestyle); 

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

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

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

谢谢

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

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

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

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

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

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

或者,Application Insights足够聪明,知道它是在unit testing中运行,而不是发送数据?

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

您应该做的是在TelemetryClient上定义一个(或者甚至多个)抽象,这些抽象是专门为您的应用程序量身定制的 。 因此,不要试图用可能的100种方法模仿TelemetryClient的API,而只是在控制器实际使用的接口上定义方法,并使它们尽可能简单,这样你就可以使控制器的代码更简单 – 和 -你的unit testing更简单。

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

 container.RegisterSingleton( new TelemetryClientAdapter(new TelemetryClient(...))); 

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

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

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

 container.RegisterSingleton(new TelemetryClientAdapter()); 

Application Insights有一个通过模拟TelemetryChannelTelemetryClient进行unit testing的示例 。

TelemetryChannel实现了ITelemetryChannel因此很容易模拟和注入。 在此示例中,您可以记录消息,然后稍后从用于断言的Items收集消息。

 public class MockTelemetryChannel : ITelemetryChannel { public IList 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); 

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

appInsightsSettings.json

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

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

使用Josh Rostad撰写的模拟TelemetryChannel并将其注入我的测试中,我取得了很大的成功。 这是模拟对象:

 public class MockTelemetryChannel : ITelemetryChannel { public ConcurrentBag SentTelemtries = new ConcurrentBag(); 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); } 

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

TelemetryConfiguration.Active.DisableTelemetry = true;