Design Pattern per fare le cose in sequenza ed essere testabili

0

Sto lavorando a un'applicazione che contiene un elemento che deve eseguire diverse chiamate http in un ordine set , per eseguire correttamente un'azione. (Interfaccia con l'API di un sistema esterno.)

Abbiamo scritto codice che è funzionale, ma ha faticato nel tentativo di scrivere appropriati unit test per coprirlo (non ultimo il HTTPClient). Questo mi ha fatto dubitare di questo odore e mi chiedo se c'è un modo migliore per scuoiarlo, forse un modello di design adatto.

Facciamo una richiesta all'altro sistema per aggiornare uno stato e una nota. Ma per farlo dobbiamo prima fare un paio di cose, per fare in modo che l'oggetto venga modificato. Dobbiamo anche usare questo approccio per alcune altre operazioni, che non abbiamo ancora scritto, come CreateNewRequest (params), così desideroso di trovare un approccio migliore.

  • Hanno sempre bisogno di correre in ordine.
  • Se uno fallisce allora l'intero lotto potrebbe non sapere fino all'elemento finale che controlla la risposta del put. Nel nel qual caso dovrebbe sempre ricominciare dall'inizio.

Quale modello di progettazione orientato agli oggetti dovrebbe essere il più adatto per questo scenario (e unità testabile)?

Ecco un esempio di ciò che stiamo facendo in questo momento, che aiuta.

async Task<bool> ChangeRequestStatusAsync(HttpClient client, int requestId, RequestStatus statusToChangeTo, string note)
    {
        var actions = await client.GetStringAsync($"{client.BaseAddress}/controller/workflow/action");
        int nextActionId = ExtractNextActionId(actions);
        int previousActionId = ExtractNextActionId(actions);

        await client.GetStringAsync($"{client.BaseAddress}/controller/workflow/action/{previousActionId}");
        await client.GetStringAsync($"{client.BaseAddress}/controller/workflow/step/taskdefinitions");
        await client.GetStringAsync($"{client.BaseAddress}/controller/workflow/request/{nextActionId}");

        string requestsJson = await client.GetStringAsync($"{client.BaseAddress}/controller/workflow/step/tasks");

        await client.GetStringAsync($"{client.BaseAddress}/controller/workflow/request/{nextActionId}");

        //Send back the modified requests list
        var modifiedJsonRequest = ModifyRequestStatus(requestId, statusToChangeTo, note, requestsJson);
        HttpContent requestToPut = new StringContent(modifiedJsonRequest, Encoding.UTF8);
        requestToPut.Headers.ContentType = new MediaTypeHeaderValue("application/json");
        var requestPut = await client.PutAsync($"{client.BaseAddress}/controller/workflow/step/tasks", requestToPut);

        //TODO: Check for success and return
    }
    
posta David C 22.02.2018 - 13:31
fonte

1 risposta

4

That made me question this smell and wonder if there's a better way of skinning it, perhaps a suitable design pattern.

Un modello comune qui è riconoscere che esiste un confine tra il tuo nucleo e la cosa-over-there-that-answers-your-http-request. C'è un protocollo che i due devono concordare.

Cercare di ottenere le solite proprietà di un test unitario (veloce, deterministico, isolato) quando parlare attraverso i confini del processo è difficile.

Un modo per imbrogliare questo è scrivere i test unitari in modo tale che il protocollo non attraversi il limite del processo. Di solito ciò avviene sostituendo la connessione al processo remoto con un test double che risponde in modo controllato.

Naturalmente, quando lo fai, i test unitari stabiliscono che il sistema in prova è compatibile con il test double, piuttosto che con il processo remoto. Puoi scrivere test che coprono questa lacuna, ma questi test sono tipicamente eseguiti in una parte diversa del ciclo di vita (test integrati, test di integrazione, test di sistema, test end-to-end ...)

Se osservi attentamente, potresti notare che il test double funge da diversa strategia per fornire risposte al protocollo. Questo naturalmente si nutre di un approccio ports-and-adapters, in cui il codice client parla di un'interfaccia e si "inserisce" la strategia di implementazione appropriata per il contesto corrente (utilizzando il test double in un processo di test e accedendo al processo remoto in gli altri).

    
risposta data 22.02.2018 - 14:35
fonte