progettazione di classi per testabilità

2

Sto scrivendo una libreria per le chiamate all'API OVH e mi sto chiedendo come renderla amichevole per i test unitari. Ho un APICliente di classe. Il costruttore di questa classe inizializza l'oggetto con tutti i parametri, ma poi tenta immediatamente di eseguire l'autenticazione. Esiste anche il metodo di invio per l'invio di richieste API autenticate generiche. Il fatto è: ho alcuni metodi di helper privati usati attraverso il codice, come Hex che trasforma i byte in hex, hash che è una scorciatoia per fare un hash SHA1 di una determinata stringa, e parseJson che analizza un json fuori dalla risposta http usando una dipendenza esterna, cioè javax.json. Devo spostare tali utilità su un'altra classe di utilità come metodi statici per poterle testare separatamente senza creare oggetti client API completi? Non credo che userò quei metodi da nessun'altra parte.

Aggiornamento:

Quando si esegue l'autenticazione e l'invio di richieste, di solito si presenta come: creare oggetti json, serializzare json, creare e inviare la richiesta http con intestazioni appropriate, ottenere risposta, analizzare la risposta come json, estrarre i dati. Sebbene i dettagli siano diversi in quanto tutte le richieste dopo l'autenticazione sono effettivamente firmate. edit2: questo è un servizio di riposo

    
posta Michał Zegan 28.03.2016 - 14:00
fonte

3 risposte

0

Snowman 's answer risolve già il problema con l'autenticazione e sono d'accordo con questo consiglio.

Riguardo ai metodi di utilità che hai menzionato, ti consiglio di inviarli in un'utilità class . Anche se ne hai bisogno solo in un posto al momento, è sbagliato avere

static String toHex(byte[] data);

o

static byte[] hashSha1(byte[] data);

in un APIClient dato che fare queste cose non fa parte della vera attività di APIClient . Inserendoli in un'utilità class dove possono essere public consente di testarli unitariamente e magari riutilizzarli in seguito.

Non ci sono costi inutili associati a questo. All'interno di APIClient , puoi fare riferimento ai metodi static nell'utility class direttamente senza dover inserire questa dipendenza. Queste funzioni di utilità sono così semplici che probabilmente non vorrai mai estinguerle per il test.

La parte più difficile è probabilmente trovare un posto decente per quei metodi di utilità. A volte, potrebbe essere allettante metterli tutti insieme in un unico Util class , ma questo diventerà rapidamente disordinato. Quindi magari raggrupparli semanticamente in "funzioni di testo", "funzioni di hash", ecc. E assicurarsi che sia intuitivo dove cercare o magari aggiungere funzionalità in seguito.

    
risposta data 30.03.2016 - 08:05
fonte
2

The constructor of this class initializes the object with all parameters, but then immediately tries to perform authentication.

Questo è il problema. Un costruttore dovrebbe costruire l'oggetto, non richiamare la funzionalità. Forse il costruttore lascia il nuovo oggetto in uno stato in cui può autenticare, ma questo è tutto.

Se è necessario eseguire test su questa classe, è possibile che si desideri utilizzare la funzionalità di simulazione. Se l'autenticazione è un problema (i test unitari in genere non dovrebbero comunicare con un'API remota, ad esempio), suddividere tale responsabilità in una classe separata. Quindi iniettare un oggetto di autenticazione fittizio.

Generalmente, vuoi prendere in giro oggetti che eseguono azioni che non sono utili nei test unitari e trasferiscili negli oggetti che veramente vogliono testare. Quindi forse l'autenticazione stessa dovrebbe essere incapsulata in una classe separata. Crea un'interfaccia, dove c'è un'implementazione "reale" e la tua implementazione simulata. Quando si costruisce un oggetto che deve utilizzare l'autenticazione, si passa a un autenticatore che sarà una simulazione nei test dell'unità, mentre il codice di produzione utilizzerà il vero autenticatore.

In altre parole, sembra che un oggetto possa avere troppa responsabilità qui.

    
risposta data 30.03.2016 - 07:22
fonte
0

Riguardo all'autenticazione, proprio come dice Snowman. La responsabilità dei costruttori è costruire l'oggetto. Quindi è necessario spostare quel codice in un nuovo metodo. Per questo hai due scelte, o introduci un metodo pubblico Authenticate() , o lascia che sia protetto ed essere chiamato dal metodo Connect() .

La prima alternativa introduce l'accoppiamento temporale. Significa che i metodi devono essere eseguiti in un ordine corretto per poter funzionare (Connect, Authenticate, AnyOtherMethod). Per i clienti che potrebbero anche essere OK come gli sviluppatori sono usati per Connect() . Tuttavia, il metodo di autenticazione porta anche l'insicurezza. In quali casi devo essere autenticato? Devo autenticarmi per tutti i metodi?

Meglio spostarlo nel metodo Connect o chiamarlo internamente in quei casi che è necessario. Non c'è davvero bisogno di farlo nel costruttore, dato che genererebbe comunque un'eccezione di runtime (e non un errore di compilazione del tempo). Quindi non importa se viene chiamato nel costruttore o quando fai qualcos'altro, giusto?

Per quanto riguarda i metodi di crittografia, stai infrangendo il Principio di Responsabilità Unica. La tua lezione ha due motivi per cambiare. Il primo è se l'API del server cambia e il secondo se la crittografia cambia.

Non sono un grande fan delle classi Util, esse tendono a violare SRP e a crescere rapidamente. Invece dovresti probabilmente introdurre una classe ApiCrypto che richiede vare di quello.

Lo stesso vale per la serializzazione. Se nel tuo client sono presenti metodi dedicati alla serializzazione, spostali in una classe ApiSerialization e chiamali dalla classe client.

Al termine, fai un passo indietro e goditi la vista testabile.

    
risposta data 30.03.2016 - 08:26
fonte

Leggi altre domande sui tag