Verifica l'indipendenza rispetto alla progettazione architettonica

5

Quindi, sto facendo un progetto che deve consumare un servizio REST. Sto usando C # e sto scrivendo io stesso con la classe HttpClient . Cerco anche di creare alcune unit test per la mia libreria, ma quella classe non lo rende facile. Qui ci sono parti del conflitto:

  1. HttpClient è progettato per essere utilizzato come singleton, come indicato da ad esempio, questo articolo.
  2. Verificare l'interazione con un servizio dovrebbe sostituire la dipendenza dal servizio reale per un falso, poiché il test unitario dovrebbe essere il più indipendente possibile.
  3. HttpClient non implementa alcuna interfaccia, quindi non puoi realizzare la tua implementazione, ma sembra che tu debba implementare HttpMessageHandler per scambiare richieste come qui
  4. HttpClient è inizializzato con HttpMessageHandler e il gestore usato non può essere modificato in fase di runtime.

Ora mi sforzo di implementare la libreria con tutto questo in mente. In questo momento ho Gestore come parametro nel singleton GetInstance, tuttavia il parametro viene utilizzato solo se è la prima chiamata e ignorato tutte le altre volte, il che sembra un cattivo design e un comportamento inaspettato. Si prega di avvisare.

    
posta Misamoto 24.07.2018 - 13:45
fonte

5 risposte

7

Un modo comune per aggirare tali classi è utilizzare il schema di delega (da non confondere con i delegati in C # ), per creare un wrapper attorno a HttpClient .

Ad esempio, supponiamo che tu usi solo GetAsync e PostAsync . Quindi si crea un'interfaccia adatta:

public interface IHttpClient
{
    Task<HttpResponseMessage> GetAsync(string uri);
    Task<HttpResponseMessage> PostAsync(string uri, HttpContent content);
}

Quindi la tua classe di runtime assomiglia a:

public class HttpClientWrapper : IHttpClient
{
    private HttpClient _client = new HttpClient();

    public async Task<HttpResponseMessage> GetAsync(string uri)
        => await _client.GetAsync(uri);

    public async Task<HttpResponseMessage> PostAsync(string uri, HttpContent content)
        => await _client.PostAsync(uri, content);
}

Per i test, devi solo creare un'implementazione fittizia di IHttpClient .

Ovviamente, se utilizzi più metodi e proprietà di HttpClient , allora espandi IHttpClient di conseguenza.

    
risposta data 24.07.2018 - 14:00
fonte
3

HttpClient doesn't implement any interface, so you can't make your own implementation

È vero.

Tuttavia, ciò che puoi fare, è presentare le tue astrazioni, che sono implementate usando HttpClient. Nei tuoi test, dove vuoi che il test sia isolato dal mondo reale, crei un test double per la tua astrazione.

La motivazione qui è analoga alle regole di decomposizione descritte da Parnas : hai preso una decisione per usare un'implementazione che dipende da HttpClient. Per isolare il resto del programma da quella decisione, lo si avvolge in un limite del modulo. Per i tuoi test, dove vuoi prendere una decisione diversa, hai un modulo diverso. Sistemando che entrambi questi moduli abbiano la stessa superficie API, ne permetti uno da utilizzare al posto dell'altro ( Liskov )

    
risposta data 24.07.2018 - 14:01
fonte
3

Non testare la tua libreria del Cliente.

Il Cliente dovrebbe preoccuparsi solo di comunicare con un server. Provarlo in isolamento non è molto utile.

Scrivi test di integrazione per i test Client + Server e Unità per il servizio dietro il livello di hosting.

    
risposta data 24.07.2018 - 14:28
fonte
1

Se io dove dovrei

  • crea un'interfaccia c # con tutta la logica di business fornita da lib che non ha alcuna dipendenza da un web / http / sessione
  • implementa tutti gli aspetti dell'indipendente logico di web / http / session che implementa la tua interfaccia
  • implementa una versione di trasporto http della stessa interfaccia che incapsula tutto il web / http / session e inoltra tutta l'elaborazione aziendale alla stessa interfaccia. questo è un sottile wrapper specifico per il trasporto http attorno alla tua logica

in questo modo è possibile testare l'unità della logica aziendale senza alcun http. Tutta la comunicazione dei client lib passa solo attraverso l'interfaccia

prova ad implementare

    
risposta data 25.07.2018 - 11:31
fonte
0

Poiché HttpClient è usato come un singleton, lo registreresti nel tuo contenitore IoC (normale, di produzione) come questo (a seconda del contenitore che stai usando-- Sto usando AutoFac):

public Container CompositionRoot()
{
    var builder = new ContainerBuilder();
    container.RegisterSingleton<HttpClient>();
    container.RegisterSingleton<HttpClientHandler>();
    return builder.Build();
}

Poiché hai registrato l'HttpClientHandler predefinito, il codice di produzione accederà alla rete, come richiesto.

Nel tuo progetto di test, devi solo registrarlo in modo diverso:

public class MyHandler : System.Net.Http.HttpClientHandler
{
    public bool WasCalled = false;

    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        WasCalled = true;
        return new HttpResponseMessage
        {
            Content = new StringContent("This is a test response.")
        };
    }
}

public Container CompositionRoot()
{
    var builder = new ContainerBuilder();
    container.RegisterSingleton<HttpClient>();
    container.RegisterSingleton<MyHandler>().As<HttpClientHandler>();
    return builder.Build();
}

O iniettalo direttamente:

//Arrange
var o = new ClassUnderTest(new HttpClient(new MyHandler()));

//Act
o.DoSomethingThatNeedsTheHttpClient();

//Assert
Assert.IsTrue(o.WasCalled);

Una volta registrata (o iniettata) la classe MyHandler , verrà automaticamente iniettata nella HttpClient quando il grafico dell'oggetto viene composto e il codice di test verrà eseguito, consentendo di isolare l'accesso alla rete.

    
risposta data 31.07.2018 - 04:56
fonte

Leggi altre domande sui tag