MVC Webアプリがあり、DIにSimple Injectorを使用しています。私のコードのほとんどすべてが単体テストでカバーされています。ただし、一部のコントローラーにテレメトリコールを追加したため、依存関係の設定に問題があります。
テレメトリコールは、Microsoft AzureがホストするApplication Insightsサービスにメトリックを送信するためのものです。アプリはAzureで実行されておらず、ISSを備えたサーバーのみです。 AIポータルは、テレメトリライブラリを使用して送信するカスタムイベントを含め、アプリケーションに関するあらゆる種類の情報を提供します。その結果、コントローラーには、インターフェイスがなく、2つのコンストラクターを持つシールされたクラスであるMicrosoft.ApplicationInsights.TelemetryClientのインスタンスが必要です。私はそのように登録してみました(ハイブリッドライフスタイルはこの質問とは無関係です。完全にするために含めただけです)。
// 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は単体テストで実行されていることを認識し、データを送信しないのに十分賢いですか?
この問題への「洞察」をいただければ幸いです。
ありがとう
Microsoft.ApplicationInsights.TelemetryClient、これにはインターフェイスがなく、2つのコンストラクターを持つシールされたクラスです。
このTelemetryClient
はフレームワークタイプであり、 フレームワークタイプはコンテナによって自動接続されるべきではありません 。
コンテナーのコンストラクター解決動作をオーバーライドする方法を示す投稿を見つけましたが、それはかなり複雑に思われます。
そうです、これは意図的なものです。なぜなら、これは アンチパターン であるため、複数のコンストラクタを使用してコンポーネントを作成しないようにするためです。
自動配線を使用する代わりに、@ qujckがすでに指摘したように、次の登録を行うことができます。
container.Register<TelemetryClient>(() =>
new TelemetryClient(/*whatever values you need*/),
requestOrTransientLifestyle);
それとも、Application Insightsは単体テストで実行されていることを認識し、データを送信しないのに十分賢いですか?
ありそうもない。このTelemetryClient
に依存するクラスをテストする場合は、代わりに偽の実装を使用して、単体テストが壊れたり遅くなったり、Insightデータを汚染したりしないようにすることをお勧めします。しかし、テストが問題にならない場合でも、 依存関係の逆転の原則 によると、(1)独自のアプリケーションで定義されている(2)抽象化に依存する必要があります。 TelemetryClient
を使用すると、両方の点で失敗します。
代わりにすべきことは、TelemetryClient
に対して1つ(またはおそらく複数)の抽象化を定義することです特にアプリケーションに合わせて調整されます。したがって、可能な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());
Application Insightsには、TelemetryClient
をモックすることによってTelemetryChannel
をユニットテストする 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);
}
抽象化/ラッパーパスをたどらない場合。テストでは、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
抽象化ルートを使用しない別のオプションは、テストを実行する前にテレメトリを無効にすることです。
TelemetryConfiguration.Active.DisableTelemetry = true;