我有一个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是否足够聪明,知道它在单元测试中运行,而不是发送数据?
对此问题的任何“见解”将不胜感激!
由于
答案 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;