2012-02-20 5 views
7

Sto scrivendo una serie di classi di raccolta in C#, ognuna delle quali implementa interfacce personalizzate simili. È possibile scrivere una singola raccolta di test unitari per un'interfaccia e eseguirli automaticamente tutti su diverse implementazioni? Vorrei evitare qualsiasi codice di test duplicato per ogni implementazione.È possibile implementare una serie di test riutilizzabili per verificare l'implementazione di un'interfaccia?

Sono disposto a esaminare qualsiasi estensione di framework (NUnit, ecc.) O Visual Studio per ottenere ciò.


Per coloro che cercano di fare lo stesso, ho postato la mia soluzione concreta, in base al largo di avandeursen's accepted solution, come an answer.

+1

Aggiunto [lsp] (http://stackoverflow.com/questions/tagged/lsp) come tag, poiché la domanda e la risposta si applicano a qualsiasi gerarchia di classi che aderiscono a LSP. – avandeursen

risposta

6

Sì, è possibile. Il trucco è lasciare che la tua gerarchia di test della classe di unità segua la gerarchia di classi del tuo codice.

Supponiamo di avere un'interfaccia Itf con classi di implementazione C1 e C2.

Per prima cosa creare una classe di test per Itf (ItfTest). Per eseguire effettivamente il test, è necessario creare un'implementazione fittizia dell'interfaccia Itf.

Tutti i test in questo ItfTest devono passare a qualsiasi implementazione di Itf (!). In caso contrario, l'implementazione non è conforme alla Liskov Substitution Principle (la "L" nel film di Martin SOLID principi di progettazione OO)

Così, per creare un banco di prova per C1, la classe C1Test può estendere ItfTest. La tua estensione dovrebbe sostituire la creazione dell'oggetto fittizio con la creazione di un oggetto C1 (inserendolo o utilizzando uno GoF factory method). In questo modo, tutti i casi ItfTest vengono applicati alle istanze di tipo C1. Inoltre, la tua classe C1Test può contenere casi di test aggiuntivi specifici per C1.

Allo stesso modo per C2. E puoi ripetere il trucco per classi e interfacce nidificate più profonde.

Riferimenti: Binder Polymorphic Server Test modello, e PACT di McGregor - Architettura parallela per prova di componenti.

+1

Per evitare false implementazioni, ho semplicemente dichiarato la mia classe 'ItfTest' come astratta e ho dichiarato una funzione astratta protetta Itf CreateInstance();' function stub. (Si noti che sia 'ItfTest' che' C1Test' devono avere l'attributo '[TestClass]'. – dlras2

+1

Sì, ho usato anche il metodo factory per quello, dal momento che è più semplice. Ho usato questo approccio di prova in JUnit, dove non esiste un attributo '[TestClass]', ma dove le annotazioni '@ Test' nelle superclassi sono ereditate causando la ri-esecuzione dei casi di superclasse in sottoclassi. E: grazie per la modifica. – avandeursen

2

Per eseguire questa operazione è possibile utilizzare gli attributi [RowTest] in MBUnit. L'esempio seguente mostra dove si passa il metodo di una stringa per indicare quale classe di implementazione di interfaccia che si desidera creare un'istanza, e quindi crea questa classe attraverso la riflessione:

[RowTest] 
[Row("Class1")] 
[Row("Class2")] 
[Row("Class3")] 
public void TestMethod(string type) 
{ 
    IMyInterface foo = Activator.CreateInstance(Type.GetType(type)) as IMyInterface; 

    //Do tests on foo: 
} 

Nel [Row] attributi, è possibile passare qualsiasi numero arbitrario dei parametri di input, come i valori di input per i test oi valori attesi da restituire mediante invocazioni di metodi. Sarà necessario aggiungere argomenti dei tipi corrispondenti come argomenti di input del metodo di prova.

+0

Puoi modificare la tua risposta per spiegare esattamente come testare le implementazioni in questo modo? Sembra che tu stia dicendo che ogni test unitario necessita di un'istruzione switch per tutte le implementazioni, che non è quello che voglio. – dlras2

+0

Non è necessario utilizzare un'istruzione switch se non si desidera. Ho detto che puoi anche usare la riflessione. Vedi la risposta di Anthony per un esempio di questo. Ho anche aggiornato il mio post per includere un esempio di utilizzo del reflection per creare un'istanza delle varie implementazioni concrete dell'interfaccia. –

+0

Scriverò probabilmente questo per prendere istanze di 'IMyInterface' invece del solo nome, ma ancora +1. – dlras2

2

Espansione alla risposta Joe's, è possibile utilizzare l'attributo [TestCaseSource] in NUnit in modo simile a Rowest di MBUnit. È possibile creare una fonte di test case con i nomi delle classi in là. È quindi possibile decorare ogni test quale attributo TestCaseSource. Quindi utilizza Activator.CreateInstance che puoi trasmettere all'interfaccia e verrai impostato.

Qualcosa di simile (nota - testa compilato)

string[] MyClassNameList = { "Class1", "Class2" }; 

[TestCaseSource(MyClassNameList)] 
public void Test1(string className) 
{ 
    var instance = Activator.CreateInstance(Type.FromName(className)) as IMyInterface; 

    ... 
} 
1

Questa è la mia concreta attuazione in base al largo di avandeursen's answer:

[TestClass] 
public abstract class IMyInterfaceTests 
{ 
    protected abstract IMyInterface CreateInstance(); 

    [TestMethod] 
    public void SomeTest() 
    { 
     IMyInterface instance = CreateInstance(); 
     // Run the test 
    } 
} 

Ogni implementazione di interfaccia definisce quindi la seguente classe di test:

[TestClass] 
public class MyImplementationTests : IMyInterfaceTests 
{ 
    protected override IMyInterface CreateInstance() 
    { 
     return new MyImplementation(); 
    } 
} 

SomeTest viene eseguito una volta per ogni calcestruzzo TestClass derivato da IMyInterfaceTests. Usando una classe base astratta, evito la necessità di eventuali implementazioni fittizie. Assicurati di aggiungere TestClassAttribute a entrambe le classi o questo non funzionerà. Infine, è possibile aggiungere eventuali test specifici dell'implementazione (come i costruttori) alla classe figlia, se lo si desidera.

+0

stai usando un particolare framework di test? Sto utilizzando i test unitari inclusi in VS2012 e non vengono rilevati i test ereditati da un altro progetto di test "condiviso" (spazio dei nomi). – drzaus

+0

strani test ereditati all'interno dello stesso progetto, diverso spazio dei nomi mostrato bene. è solo quando si trovano in un altro progetto che non si stanno registrando. cosa mi manca? – drzaus

Problemi correlati