将 Application Insights 与单元测试结合使用?
Using Application Insights with Unit Tests?
我有一个 MVC 网络应用程序,我正在使用 Simple Injector for DI。单元测试几乎覆盖了我所有的代码。然而,现在我已经在一些控制器中添加了一些遥测调用,我在设置依赖项时遇到了问题。
遥测调用用于将指标发送到 Microsoft Azure 托管的 Application Insights 服务。该应用程序不在 Azure 中 运行ning,只是一个带有 ISS 的服务器。 AI 门户告诉您有关应用程序的各种信息,包括您使用遥测库发送的任何自定义事件。因此,控制器需要 Microsoft.ApplicationInsights.TelemetryClient 的实例,它没有接口,是一个密封的 class,具有 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 抱怨并未能通过验证。我发现 post 展示了如何覆盖容器的构造函数解析行为,但这看起来相当复杂。首先我想备份并问这个问题:
如果我不使 TelemetryClient 成为注入的依赖项(只需在 class 中创建一个新的依赖项),该遥测是否会在单元测试的每个 运行 中发送到 Azure,制造大量虚假数据?或者 Application Insights 是否足够聪明,知道它正在 运行 单元测试中,而不发送数据?
如果 "Insights" 参与此问题,我们将不胜感激!
谢谢
Microsoft.ApplicationInsights.TelemetryClient, which has no Interface and is a sealed class, with 2 constructors.
这个TelemetryClient
是框架类型,framework types should not be auto-wired by your container。
I found a post showing how to override the container's constructor resolution behavior, but that seems pretty complicated.
是的,这种复杂性是故意的,因为我们不希望人们创建具有多个构造函数的组件,因为这是 an anti-pattern。
正如@qujck 已经指出的那样,您可以不使用自动连接,只需进行以下注册:
container.Register<TelemetryClient>(() =>
new TelemetryClient(/*whatever values you need*/),
requestOrTransientLifestyle);
Or is Application Insights smart enough to know it is running in a unit test, and not send the data?
不太可能。如果您想测试依赖于此 TelemetryClient
的 class,您最好使用伪造的实现,以防止您的单元测试变得脆弱、缓慢或污染您的 Insight 数据。但是,即使测试不是问题,根据 Dependency Inversion Principle,您也应该依赖 (1) 由您自己的应用程序定义的 (2) 抽象。使用 TelemetryClient
.
时这两点都失败了
您应该做的是在 TelemetryClient
上定义一个(或什至多个)抽象,这些抽象 专门为您的应用程序量身定制 。所以不要试图模仿 TelemetryClient
的 API 及其可能的 100 种方法,而是只在您的控制器实际使用的接口上定义方法,并使它们像 一样简单可能 这样您就可以使控制器的代码更简单 - 并且您的单元测试更简单。
定义好抽象后,您可以创建一个在内部使用 TelemetryClient
的适配器实现。我想象您按如下方式注册此适配器:
container.RegisterSingleton<ITelemetryLogger>(
new TelemetryClientAdapter(new TelemetryClient(...)));
这里我假设 TelemetryClient
是线程安全的并且可以作为单例工作。否则,您可以这样做:
container.RegisterSingleton<ITelemetryLogger>(
new TelemetryClientAdapter(() => new TelemetryClient(...)));
这里的适配器仍然是单例,但提供了一个允许创建 TelemetryClient
的委托。另一种选择是让适配器在内部创建(并可能处置)TelemetryClient
。这也许会使注册变得更加简单:
container.RegisterSingleton<ITelemetryLogger>(new TelemetryClientAdapter());
如果您不想走抽象/包装路径。在您的测试中,您可以简单地将 AppInsights 端点定向到模拟轻量级 http 服务器(这在 ASP.NET Core 中很简单)。
appInsightsSettings.json
"ApplicationInsights": {
"Endpoint": "http://localhost:8888/v2/track"
}
如何在 ASP.NET 核心中设置 "TestServer" http://josephwoodward.co.uk/2016/07/integration-testing-asp-net-core-middleware
不走抽象路线的另一种选择是在进行 运行 测试之前禁用遥测:
TelemetryConfiguration.Active.DisableTelemetry = true;
Application Insights 通过模拟 TelemetryChannel
对 TelemetryClient
进行了 example 单元测试。
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);
我在使用 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);
}
基于此处的其他工作;
- 创建频道 - 如果需要,您可以使用它来测试遥测
public class MockTelemetryChannel : ITelemetryChannel
{
public ConcurrentBag<ITelemetry> SentTelemtries = new();
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()
{
}
}
- 使用一个漂亮的小静态工厂class
public static class MockTelemetryClient
{
public static TelemetryClient Create()
{
var mockTelemetryChannel = new MockTelemetryChannel();
var mockTelemetryConfig = new TelemetryConfiguration
{
TelemetryChannel = mockTelemetryChannel,
InstrumentationKey = Guid.NewGuid().ToString()
};
var mockTelemetryClient = new TelemetryClient(mockTelemetryConfig);
return mockTelemetryClient;
}
}
- 致电
MockTelemetryClient.Create()
获取您的TelemetryClient
- 利润
我的一位同事编写了这个有用的库,它引入了一些核心遥测类型的抽象(例如 ITelemetryClient
和 IMetric
)。
https://github.com/thomhurst/ApplicationInsights.TelemetryLogger
非常容易实施。您几乎不需要更改生产代码中的任何内容,测试中的模拟变得轻而易举。这是自述文件的摘录:
依赖注入
正常调用AddApplicationInsightsTelemetry()
,然后调用AddApplicationInsightsTelemetryClientInterfaces()
public void ConfigureServices(IServiceCollection services)
{
services
.AddApplicationInsightsTelemetry()
.AddApplicationInsightsTelemetryClientInterfaces();
}
ITelemetryClient
想要和TelemetryClient
一样的用法吗?将 ITelemetryClient
注入你的 类。它具有 TelemetryClient
的所有可用方法(除了不应调用的任何方法。例如内部或已弃用)。
public class MyClass
{
private readonly ITelemetryClient _telemetryClient;
public MyClass(ITelemetryClient telemetryClient)
{
_telemetryClient = telemetryClient;
}
public void DoSomething()
{
_telemetryClient.TrackTrace("Something happened");
}
}
我有一个 MVC 网络应用程序,我正在使用 Simple Injector for DI。单元测试几乎覆盖了我所有的代码。然而,现在我已经在一些控制器中添加了一些遥测调用,我在设置依赖项时遇到了问题。
遥测调用用于将指标发送到 Microsoft Azure 托管的 Application Insights 服务。该应用程序不在 Azure 中 运行ning,只是一个带有 ISS 的服务器。 AI 门户告诉您有关应用程序的各种信息,包括您使用遥测库发送的任何自定义事件。因此,控制器需要 Microsoft.ApplicationInsights.TelemetryClient 的实例,它没有接口,是一个密封的 class,具有 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 抱怨并未能通过验证。我发现 post 展示了如何覆盖容器的构造函数解析行为,但这看起来相当复杂。首先我想备份并问这个问题:
如果我不使 TelemetryClient 成为注入的依赖项(只需在 class 中创建一个新的依赖项),该遥测是否会在单元测试的每个 运行 中发送到 Azure,制造大量虚假数据?或者 Application Insights 是否足够聪明,知道它正在 运行 单元测试中,而不发送数据?
如果 "Insights" 参与此问题,我们将不胜感激!
谢谢
Microsoft.ApplicationInsights.TelemetryClient, which has no Interface and is a sealed class, with 2 constructors.
这个TelemetryClient
是框架类型,framework types should not be auto-wired by your container。
I found a post showing how to override the container's constructor resolution behavior, but that seems pretty complicated.
是的,这种复杂性是故意的,因为我们不希望人们创建具有多个构造函数的组件,因为这是 an anti-pattern。
正如@qujck 已经指出的那样,您可以不使用自动连接,只需进行以下注册:
container.Register<TelemetryClient>(() =>
new TelemetryClient(/*whatever values you need*/),
requestOrTransientLifestyle);
Or is Application Insights smart enough to know it is running in a unit test, and not send the data?
不太可能。如果您想测试依赖于此 TelemetryClient
的 class,您最好使用伪造的实现,以防止您的单元测试变得脆弱、缓慢或污染您的 Insight 数据。但是,即使测试不是问题,根据 Dependency Inversion Principle,您也应该依赖 (1) 由您自己的应用程序定义的 (2) 抽象。使用 TelemetryClient
.
您应该做的是在 TelemetryClient
上定义一个(或什至多个)抽象,这些抽象 专门为您的应用程序量身定制 。所以不要试图模仿 TelemetryClient
的 API 及其可能的 100 种方法,而是只在您的控制器实际使用的接口上定义方法,并使它们像 一样简单可能 这样您就可以使控制器的代码更简单 - 并且您的单元测试更简单。
定义好抽象后,您可以创建一个在内部使用 TelemetryClient
的适配器实现。我想象您按如下方式注册此适配器:
container.RegisterSingleton<ITelemetryLogger>(
new TelemetryClientAdapter(new TelemetryClient(...)));
这里我假设 TelemetryClient
是线程安全的并且可以作为单例工作。否则,您可以这样做:
container.RegisterSingleton<ITelemetryLogger>(
new TelemetryClientAdapter(() => new TelemetryClient(...)));
这里的适配器仍然是单例,但提供了一个允许创建 TelemetryClient
的委托。另一种选择是让适配器在内部创建(并可能处置)TelemetryClient
。这也许会使注册变得更加简单:
container.RegisterSingleton<ITelemetryLogger>(new TelemetryClientAdapter());
如果您不想走抽象/包装路径。在您的测试中,您可以简单地将 AppInsights 端点定向到模拟轻量级 http 服务器(这在 ASP.NET Core 中很简单)。
appInsightsSettings.json
"ApplicationInsights": {
"Endpoint": "http://localhost:8888/v2/track"
}
如何在 ASP.NET 核心中设置 "TestServer" http://josephwoodward.co.uk/2016/07/integration-testing-asp-net-core-middleware
不走抽象路线的另一种选择是在进行 运行 测试之前禁用遥测:
TelemetryConfiguration.Active.DisableTelemetry = true;
Application Insights 通过模拟 TelemetryChannel
对 TelemetryClient
进行了 example 单元测试。
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);
我在使用 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);
}
基于此处的其他工作;
- 创建频道 - 如果需要,您可以使用它来测试遥测
public class MockTelemetryChannel : ITelemetryChannel
{
public ConcurrentBag<ITelemetry> SentTelemtries = new();
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()
{
}
}
- 使用一个漂亮的小静态工厂class
public static class MockTelemetryClient
{
public static TelemetryClient Create()
{
var mockTelemetryChannel = new MockTelemetryChannel();
var mockTelemetryConfig = new TelemetryConfiguration
{
TelemetryChannel = mockTelemetryChannel,
InstrumentationKey = Guid.NewGuid().ToString()
};
var mockTelemetryClient = new TelemetryClient(mockTelemetryConfig);
return mockTelemetryClient;
}
}
- 致电
MockTelemetryClient.Create()
获取您的TelemetryClient
- 利润
我的一位同事编写了这个有用的库,它引入了一些核心遥测类型的抽象(例如 ITelemetryClient
和 IMetric
)。
https://github.com/thomhurst/ApplicationInsights.TelemetryLogger
非常容易实施。您几乎不需要更改生产代码中的任何内容,测试中的模拟变得轻而易举。这是自述文件的摘录:
依赖注入
正常调用AddApplicationInsightsTelemetry()
,然后调用AddApplicationInsightsTelemetryClientInterfaces()
public void ConfigureServices(IServiceCollection services)
{
services
.AddApplicationInsightsTelemetry()
.AddApplicationInsightsTelemetryClientInterfaces();
}
ITelemetryClient
想要和TelemetryClient
一样的用法吗?将 ITelemetryClient
注入你的 类。它具有 TelemetryClient
的所有可用方法(除了不应调用的任何方法。例如内部或已弃用)。
public class MyClass
{
private readonly ITelemetryClient _telemetryClient;
public MyClass(ITelemetryClient telemetryClient)
{
_telemetryClient = telemetryClient;
}
public void DoSomething()
{
_telemetryClient.TrackTrace("Something happened");
}
}