Test di una classe che utilizza l'interfaccia esterna

2

Ho una classe che utilizza internamente un'interfaccia definita esternamente. Testare questa classe diventa difficile dal momento che ho bisogno di prendere in giro questa interfaccia, ma mi viene richiesto di fare riferimento alla libreria esterna (che potrebbe essere abbastanza grande) poiché il test è in un progetto di test separato. Ci sono modi per disaccoppiare questa interfaccia in modo che io possa prendere in giro l'interfaccia e testarla senza dover includere il progetto esterno o è un problema ereditario di come è stata configurata la mia soluzione?

    
posta Francois du Plessis 18.12.2017 - 10:59
fonte

3 risposte

2

Il post di Bob Martin L'architettura pulita ti consiglia di le dipendenze della classe dovrebbero puntare verso l'interno, non verso l'esterno.

The overriding rule that makes this architecture work is The Dependency Rule. This rule says that source code dependencies can only point inwards. Nothing in an inner circle can know anything at all about something in an outer circle. In particular, the name of something declared in an outer circle must not be mentioned by the code in the an inner circle. That includes, functions, classes. variables, or any other named software entity.

Seguito alla lettera significa che la tua biblioteca interna non farebbe nemmeno riferimento a una libreria esterna. Invece, definirebbe le proprie interfacce e quindi le librerie aggiuntive creerebbero composizioni che combinano le vostre classi interne con le implementazioni delle interfacce da cui dipende.

(Non l'ho mai visto, non lo sto deridendo come poco pratico, sembra fantastico e voglio implementarlo su larga scala prima che muoia.)

Su una scala più piccola puoi ottenere alcuni dei vantaggi assicurando che le tue classi dipendano da interfacce definite dall'utente, non su interfacce definite esternamente.

Usando un esempio molto semplice, potresti definire la tua interfaccia di registrazione, come

public interface ILogger
{
    void LogException(Exception ex); 
}

Ora se una classe ha bisogno di registrare le eccezioni, dipende solo da quell'interfaccia, non da un'interfaccia Microsoft o da un'interfaccia di Windsor, o qualsiasi altra cosa. Quindi, se stai utilizzando una libreria di log esterna, crei un'implementazione di ILogger che include una chiamata a quella libreria.

La cosa fantastica di questo è che ora stai raggiungendo la segregazione dell'interfaccia. Le tue classi hanno meno probabilità di dipendere da qualsiasi interfaccia gigante che il mondo gli fornisce e più probabilmente dipenderanno da interfacce di piccole dimensioni che forniscono esattamente ciò di cui la classe ha bisogno.

Quindi, tornando alla tua domanda iniziale, quelle interfacce saranno facili da prendere in giro. Potrebbero anche essere così piccoli da rendere più facile l'uso di semplici doppie classi di test anziché di una struttura di derisione. Mi piace usare Moq ma a volte tutte queste impostazioni diventano difficili da leggere. Se posso facilmente scrivere una classe come PermissionsValidatorThatAlwaysReturnsTrue , allora potrei andare avanti e scrivere le poche righe di codice in più. Il tempo potrebbe essere salvato quando qualcuno vede quella classe usata in un test, la naviga e vede quello che fa, invece di cercare di capire quale% di co_de viene usato e come è stato impostato.

    
risposta data 22.12.2017 - 20:48
fonte
1

Forse potresti usare un framework plug-in come MEF (Managed Extensibility Framework) per disaccoppiare la DLL / Project. Ma come suggerisce @Emerson, la creazione di un wrapper potrebbe essere più fattibile.

    
risposta data 18.12.2017 - 15:57
fonte
1

La risposta migliore è fare riferimento all'assembly che contiene l'interfaccia. Certo, è grande, ma allora cosa ... questo non innescherà nessun caricamento di assembly aggiuntivo perché la libreria esterna dovrebbe essere caricata comunque, in modo che la classe sotto test funzioni.

Ma se per qualche ragione non vuoi veramente impostare quel riferimento, è possibile fare ciò che vuoi con un po 'di riflessione. Ecco un esempio che supporta un metodo in prova che richiede un parametro conforme all'interfaccia senza riferimenti.

Per prima cosa, aggiungi questo metodo di estensione:

public static class ExtensionMethods
{
    public static void CallWithMockedParameter(this object objectUnderTest, string methodName, object valuesForMockedParameter)
    {
        var typeUnderTest = objectUnderTest.GetType();
        var methodUnderTest = typeUnderTest.GetMethod(methodName, BindingFlags.Public | BindingFlags.Instance);
        var parameterUnderTest = methodUnderTest.GetParameters()[0];
        var parameterType = parameterUnderTest.ParameterType;
        var typeToPass = parameterType.Assembly.GetTypes().Where( a => parameterType.IsAssignableFrom(a) && a.IsClass).Single();
        var objectToPass = Activator.CreateInstance(typeToPass);
        foreach (var propertyToCopy in valuesForMockedParameter.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance))
        {
            typeToPass.GetProperty(propertyToCopy.Name).SetValue(objectToPass, propertyToCopy.GetValue(valuesForMockedParameter));
        }
        methodUnderTest.Invoke(objectUnderTest, new[] { objectToPass });
    }
}

Questo bit di codice trova il tipo per la classe in prova, trova il metodo di interesse, identifica il tipo del suo parametro e trova una classe concreta che corrisponda all'interfaccia del parametro. Crea quindi un'istanza della classe concreta e la popola con i valori forniti in un tipo anonimo eseguendo il mapping delle proprietà una per una.

Con quanto sopra, puoi sostituire questa chiamata normale:

//Normal execution
ClassUnderTest o1 = new ClassUnderTest();
IExternalInterface a1 = new ExternalClass { Text = "Normal" };
o1.MethodUnderTest(a1);

Con questo:

//Mocked
var o2 = new ClassUnderTest();
var a2 = new { Text = "Mocked" };
o2.CallWithMockedParameter(nameof(o2.MethodUnderTest), a2);

Guarda il mio esempio operativo su DotNetFiddle .

    
risposta data 22.12.2017 - 21:36
fonte

Leggi altre domande sui tag