Design verificabile per una classe che può essere istanziata solo attraverso un metodo statico

1

Sto tentando di progettare una classe che deve essere istanziata attraverso l'uso di un metodo statico, qualcosa come newInstance(param1, param2) . La ragione di questo è che 2 dei 4 parametri necessari provengono dallo stesso pacchetto, ma voglio nasconderli dal pubblico .

Per raggiungere questo obiettivo, ho reso privato il costruttore. Tuttavia, mi rendo conto che ora non riesco a passare i 2 parametri sui miei test.

Va così

class Foo {
    private constructor(p1, p2, p3, p4) {}

    public static newInstance(p1, p2) {
        p3 = new p3;
        p4 = new p4;
        return new static(p1, p2, p3, p4);
    }
}

Ovviamente il mio design è sbagliato, ma non riesco a pensare ad un modo per astrarre l'esistenza di p3 e p4 ad altri sviluppatori, pur continuando a passarli come dipendenza per applicare l'inversione di dipendenza.

Quale design posso utilizzare per raggiungere questo obiettivo? Forse dovrei semplicemente creare un FooFactory e documentare che dovrebbero utilizzarlo ...

Sto praticamente scrivendo un SDK per un'API REST. Queste API REST non sono progettate da me né dalla mia azienda (quindi non posso rendere l'API REST conforme agli standard del settore) e ha un meccanismo di autenticazione molto complicato. È abbastanza complicato che voglio ottenere quanto segue:

  1. Scomposizione in piccoli componenti testabili, in modo che sia più semplice da gestire, e gli altri sviluppatori sono in grado di lavorare solo con gli endpoint esposti dall'SDK, che incapsulano il modello di dominio.
  2. Riassunto la costruzione del grafico delle dipendenze per l'SDK, in modo che possano semplicemente utilizzare la classe CompanyClient e chiamare metodi del dominio aziendale come getCustomerList .
  3. Estrarre completamente la costruzione delle intestazioni, parametri, ecc. personalizzati della richiesta HTTP.

Sono d'accordo che questo lavoro sia una unità, quindi ha senso solo testare la classe CompanyAPI da sola; Tuttavia, ciò renderebbe difficile mantenere i sottomeccanismi necessari per farlo funzionare.

In sintesi, suddividendolo in parti più piccole, è facile verificare che ogni meccanismo funzioni secondo le specifiche.

Per questo motivo voglio solo esporre 2 parametri per gli utenti della biblioteca. Non hanno bisogno di conoscere l'implementazione sottostante. Tuttavia, ho bisogno di testare e dato che le dipendenze includono un client HTTP, ho bisogno di iniettare oggetti finti.

    
posta Christopher Francisco 28.08.2017 - 18:39
fonte

2 risposte

4

Se stai utilizzando c #, puoi utilizzare questo approccio "amico":

Aggiungi un altro costruttore (possibilmente senza parametri) alla classe esclusivamente per il tuo progetto di test.

"Nascondi" il costruttore da altri sviluppatori rendendolo interno . È quindi possibile rendere visibile il costruttore al progetto di test utilizzando InternalsVisibleToAttribute .

[assembly:InternalsVisibleTo("MyUnitTestProject")]

class Foo {
    internal constructor(p1, p2, p3, p4) {}

    public static newInstance(p1, p2) {
        p3 = new p3;
        p4 = new p4;
        return new static(p1, p2, p3, p4);
    }
}

Se non stai usando c #, o preferisci non usare gli attributi "amico":

  1. Crea un factory non statico per questa classe, insieme a un'interfaccia per la fabbrica, ad es. IFooFactory .

    interface IFooFactory { Foo newInstance(p1, p2); }
    
    class FooFactory : IFooFactory
    {
        public Foo newInstance(p1, p2)
        {
            return new Foo(p1, p2, new p3(), new p4());
        }
    }
    
  2. Nel codice di produzione, inserisci la fabbrica non statica in qualsiasi classe che deve essere in grado di creare un'istanza di Foo . Utilizzare il contenitore di iniezione per assicurarsi che vi sia una sola istanza della fabbrica per ogni filo / processo / qualsiasi cosa sia appropriata per la propria applicazione.

    class SomeOperation
    {
        private readonly IFooFactory _fooFactory;
    
        public SomeOperation(IFooFactory fooFactory)
        {
            _foofactory = fooFactory;
        }
    }
    
    
    class CompositionRoot
    {
        public RegisterDependencies(IContainer container)
        {
            container.RegisterType<IFooFactory, FooFactory>().instancePerProcess();  //Syntax may vary
            container.RegisterType<SomeOperation, SomeOperation>();
        }
    }
    
  3. Sostituisci qualsiasi chiamata a

    Foo.newInstance(p1, p2)
    

    con

    _fooFactory.newInstance(p1, p2);
    
  4. Nel codice di test, scrivi la tua fabbrica di test conforme a IFooFactory e inietta invece del factory di produzione. La tua fabbrica di test potrebbe essere in grado di fare a meno di fornire dummy p3 e p4 oppure puoi stubare anche quelli.

risposta data 28.08.2017 - 20:51
fonte
3

In base a ciò che desideri, si sente Foo , insieme a p3 e p4 tutti formano un'unità. Come tali, dovrebbero essere testati insieme come un'unità. Il che significa che l'istanziazione di Foo attraverso il metodo statico è perfettamente soddisfacente per il test dell'unità.

Se ciò comporta test troppo complicati, significa che la tua decisione di non essere in grado di creare un'istanza di Foo con classi diverse potrebbe non essere corretta.

    
risposta data 28.08.2017 - 19:10
fonte