8

Possiedo un'app Web MVC e sto utilizzando Simple Injector per DI. Quasi tutto il mio codice è coperto da test unitari. Tuttavia, ora che ho aggiunto alcune chiamate di telemetria in alcuni controller, ho problemi a configurare le dipendenze.Utilizzo delle informazioni sulle applicazioni con i test delle unità?

Le chiamate di telemetria sono per l'invio di metriche al servizio Application Insights ospitato da Microsoft Azure. L'app non è in esecuzione in Azure, ma solo un server con ISS. Il portale AI ti dice tutti i tipi di cose sulla tua applicazione, compresi gli eventi personalizzati che invii usando la libreria di telemetria. Di conseguenza, il controller richiede un'istanza di Microsoft.ApplicationInsights.TelemetryClient, che non ha un'interfaccia ed è una classe sealed, con 2 costruttori. Ho provato a registrarlo in questo modo (lo stile di vita ibrido non è correlata a questa domanda, ho solo incluso per completezza):

 // 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); 

Il problema è che, poiché TelemetryClient ha 2 costruttori, SI lamenta e non riesce la convalida. Ho trovato un post che mostra come sovrascrivere il comportamento di risoluzione del costruttore del contenitore, ma ciò sembra piuttosto complicato. Per prima cosa volevo eseguire il backup e porre questa domanda:

Se non faccio in modo che TelemetryClient sia una dipendenza iniettata (basta crearne uno nuovo nella classe), tale telemetria verrà inviata ad Azure ad ogni esecuzione dell'unità test, creando molti dati falsi? Oppure Application Insights è abbastanza intelligente da sapere che è in esecuzione in un test unitario e non invia i dati?

Qualsiasi "approfondimento" in questo numero sarebbe molto apprezzato!

Grazie

+2

non posso fare con il lato AI della questione, ma la registrazione può essere effettuata semplicemente registrando un delegato che si rivolge a un costruttore specifico: 'container.Register (() => nuovo TelemetryClient (/ * qualunque sia il costruttore che si desidera utilizzare come target * /), requestOrTransientLifestyle);'. Controlla anche [DefaultScopedLifestyle] (https://simpleinjector.readthedocs.org/en/latest/lifetimes.html#scoped) – qujck

+2

Per il test delle unità, devi definire la tua astrazione su TelemetryClient che puoi prendere in giro come richiesto. I test unitari non dovrebbero parlare con Azure. – qujck

+0

Il tuo ambito ibrido personalizzato mi preoccupa. Mescolare lo stile di vita della richiesta web con uno stile di vita transitorio di solito non è una buona pratica. Può spiegare perché hai bisogno di questo stile di vita misto? – Steven

risposta

14

Microsoft.ApplicationInsights.TelemetryClient, che non ha interfaccia ed è una classe chiusa, con 2 costruttori.

Questo TelemetryClient è un tipo di struttura e framework types should not be auto-wired by your container.

Ho trovato un post che mostra come sovrascrivere il comportamento di risoluzione del costruttore del contenitore, ma ciò sembra piuttosto complicato.

Sì, questa complessità è intenzionale, perché vogliamo scoraggiare le persone dalla creazione di componenti con più costruttori, perché questo è an anti-pattern.

Invece di utilizzare auto-cablaggio, è possibile, come @qujck già sottolineato, è sufficiente effettuare la seguente registrazione:

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

O è Insights applicazione abbastanza intelligente per sapere che è in esecuzione in un'unità test, e non inviare i dati?

Molto improbabile. Se si desidera testare la classe che dipende da questo TelemetryClient, è preferibile utilizzare un'implementazione falsa, per evitare che il test dell'unità diventi fragile, lento o per inquinare i dati di Insight. Ma anche se il test non è un problema, secondo lo Dependency Inversion Principle dovresti dipendere da (1) astrazioni che sono (2) definite dalla tua applicazione. Fallisci entrambi i punti quando usi lo TelemetryClient.

Quello che dovresti fare invece è definire una (o forse anche più) astrazioni su TelemetryClient che sono specialmente su misura per la tua applicazione. Quindi non provare a imitare l'API di TelemetryClient con i suoi possibili 100 metodi, ma solo definire i metodi sull'interfaccia effettivamente utilizzati dal controller e renderli il più semplice possibile in modo da semplificare sia il codice del controllore - e- la tua unità test più semplice.

Dopo aver definito una buona astrazione, è possibile creare un'implementazione dell'adattatore che utilizza internamente lo TelemetryClient. Mi immagine si registra questo adattatore come segue:

container.RegisterSingleton<ITelemetryLogger>(
    new TelemetryClientAdapter(new TelemetryClient(...))); 

Qui Si considera che il TelemetryClient è thread-safe e può funzionare come un Singleton. In caso contrario, si può fare qualcosa di simile:

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

Ecco l'adattatore è ancora un Singleton, ma è dotato di un delegato che permette la creazione della TelemetryClient. Un'altra opzione è lasciare che l'adattatore crei (e forse disponga) il TelemetryClient internamente. Ciò renderebbe forse la registrazione ancora più semplice:

container.RegisterSingleton<ITelemetryLogger>(new TelemetryClientAdapter()); 
+2

Grazie per questa risposta dettagliata. Come hanno suggerito Steve e @qujck, alla mia domanda principale è stata data una semplice enfatizzazione per registrare il telemetryclient con il costruttore di mia scelta (sebbene la risposta di Steve abbia un refuso - contenitore. Il registro è duplicato). Dopo aver postato questa domanda, sono arrivato praticamente alla stessa soluzione per il problema del test delle unità: il wrapping di TelemetryClient in una semplice classe del mio progetto, l'esposizione dell'unico metodo di cui ho bisogno, la creazione di un'interfaccia per esso, ecc. Quindi nel test dell'unità, quella semplice classe wrapper è Mocked with Moq. –

2

Se non si desidera scendere il percorso di astrazione/wrapper. Nei test è possibile semplicemente indirizzare l'endpoint AppInsights a un server http simulato leggero (che è banale in ASP.NET Core).

appInsightsSettings.json

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

Come impostare "TestServer" in ASP.NET core http://josephwoodward.co.uk/2016/07/integration-testing-asp-net-core-middleware