2013-03-22 14 views
13

Diciamo che ho due implementazioni di un algoritmo di ricerca che restituiscono lo stesso risultato per lo stesso input. Entrambi implementano la stessa interfaccia.stesso test di unità per diverse implementazioni

Come è possibile utilizzare un singolo [TestClass] per testare entrambe le implementazioni, piuttosto che creare due file di test con la stessa logica?

Posso dire a MSUnit di avviare uno dei test due volte con diversi parametri del costruttore?
Forse dovrei (n) iniettarlo in qualche modo?

+0

Nella tua domanda chiedi informazioni su MSTest, ma nei tuoi tag specifichi NUnit. Per quale risposta vuoi? –

risposta

11

Utilizzare un abstract test class:

[TestClass] 
public abstract class SearchTests 
{ 
    private ISearcher _searcherUnderTest; 

    [TestSetup] 
    public void Setup() 
    { 
     _searcherUnderTest = CreateSearcher(); 
    } 

    protected abstract ISearcher CreateSearcher(); 

    [TestMethod] 
    public void Test1(){/*do stuff to _searcherUnderTest*/ } 

    // more tests... 

    [TestClass] 
    public class CoolSearcherTests : SearcherTests 
    { 
     protected override ISearcher CreateSearcher() 
     { 
      return new CoolSearcher(); 
     } 
    } 

    [TestClass] 
    public class LameSearcherTests : SearcherTests 
    { 
     protected override ISearcher CreateSearcher() 
     { 
      return new LameSearcher(); 
     } 
    } 
} 
+1

E questa è l'unica buona soluzione. –

1

Preferirei avere due in uno solo [TestClass] ogni test solo una implementazione: in questo modo un test in errore indicherà sempre correttamente quale implementazione è andata storta.

+2

E i due metodi [TestMethod] possono essere semplicemente metodi a una riga che chiamano entrambi lo stesso metodo che contiene effettivamente il codice di test. – Polyfun

+0

Ci sono ~ 10 diversi metodi di test per l'implementazione di ricerca che ho. Fondamentalmente suggerisci di copiare questi metodi in 20 metodi nella stessa classe (o 10 in due diverse classi di test) – Alex

+0

@ShellShock, beh, questo è fondamentalmente il modo in cui lo implemento ora, ma sto cercando un po 'di built-in o pronto funzionalità per quello. – Alex

1

Se si utilizza NUnit si può passare attraverso una variabile dichiarata in un attributo http://www.nunit.org/index.php?p=testCase&r=2.5.6

se si utilizza qualcosa come:

[TestCase(1)] 
[TestCase(2)] 
public void Test(int algorithm) 
{ 
//..dostuff 
} 

se verrà eseguito una volta per 1, una volta per 2, usi lo stesso setup/teardown troppo :)

non c'è un equivalente in MSTest tuttavia è possibile fudge in qualche modo come spiegato qui: Does MSTest have an equivalent to NUnit's TestCase?

2

Hai taggato la tua domanda con NUnit, ma chiedi informazioni su MSTest. Ciò che state chiedendo può essere ottenuto con proiettori parametrizzati in NUnit. Non ho familiarità con MSTest per suggerire un approccio equivalente lì, e una ricerca rapida indica che MSTest potrebbe non avere questa funzione.

In NUnit parametrizzare il dispositivo di prova applicando più attributi [TestFixture(...)] alla classe di dispositivo con parametri diversi. Questi parametri verranno passati al costruttore della fixture.

Poiché esistono limiti sui tipi di parametro che è possibile passare, è necessario passare una stringa specificando l'algoritmo, quindi nel costruttore assegnare il delegato o l'oggetto che fornisce l'algoritmo di ricerca a un campo membro che viene utilizzato nei test.

Ad esempio:

using System; 
using System.Collections.Generic; 
using NUnit.Framework; 

namespace MyTests 
{ 
    public static class SearchAlgorithms 
    { 
     public static int DefaultSearch(int target, IList<int> data) 
     { 
      return data.IndexOf(target); 
     } 

     public static int BrokenSearch(int target, IList<int> data) 
     { 
      return 789; 
     } 
    } 

    [TestFixture("forward")] 
    [TestFixture("broken")] 
    public class SearchTests 
    { 
     private Func<int, IList<int>, int> searchMethod; 

     public SearchTests(string algorithmName) 
     { 
      if (algorithmName == "forward") 
      { 
       this.searchMethod = SearchAlgorithms.DefaultSearch; 
       return; 
      } 

      if (algorithmName == "broken") 
      { 
       this.searchMethod = SearchAlgorithms.BrokenSearch; 
      } 
     } 

     [Test] 
     public void SearchFindsCorrectIndex() 
     { 
      Assert.AreEqual(
       1, this.searchMethod(2, new List<int> { 1, 2, 3 })); 
     } 

     [Test] 
     public void SearchReturnsMinusOneWhenTargetNotPresent() 
     { 
      Assert.AreEqual(
       -1, this.searchMethod(4, new List<int> { 1, 2, 3 })); 
     } 
    } 
} 
0

Non posso dire che sono estremamente soddisfatto di questo approccio, ma ecco quello che ho finito per fare. Sono quindi andato a cercare un approccio migliore e ho trovato questa domanda. Questo approccio soddisfa i criteri, 1) Sto usando MS Test, 2) Scrivo la logica di test solo 1 volta, 3) Posso dire quale implementazione è fallita (e il doppio clic sul test mi porterà alla giusta classe di test) . Questo approccio utilizza una classe di base per contenere tutta la logica di test effettiva e quindi una classe derivata per ogni implementazione (I have 3) che imposta l'implementazione specifica nell'interfaccia di base e sovrascrive i metodi di test di base.

[TestClass] 
public abstract class SearchTestBase 
{ 
    protected ISearcher Searcher { get; set; } 

    [TestMethod] 
    public virtual void Find_Results_Correct() 
    { 
     // Arrange (code here) 
     // Act (single line here) 
     var actual = Searcher.Results(input); 
     // Assert 
    } 
} 

(different file...) 
[TestClass] 
public class FastSearcherTest : SearcherTestBase 
{ 
    [TestInitialize] 
    public void TestInitialize() 
    { 
     Searcher = new FastSearcher(); 
    } 

    [TestMethod] 
    public override void Find_Results_Correct() 
    { 
     base.Find_Results_Correct(); 
    } 
} 

(different file...) 
[TestClass] 
public class ThoroughSearcherTest : SearcherTestBase 
{ 
    [TestInitialize] 
    public void TestInitialize() 
    { 
     Searcher = new ThoroughSearcher(); 
    } 

    [TestMethod] 
    public override void Find_Results_Correct() 
    { 
     base.Find_Results_Correct(); 
    } 
} 

Quindi quello che non mi piace di questo approccio è che ogni volta che vuole aggiungere un test ho bisogno di andare a ciascuno dei file di prova e sovrascrivere il nuovo metodo di prova. Quello che mi piace sono i 3 requisiti che hai avuto.Se ho bisogno di cambiare un test, cambio la logica in un solo punto. Il vantaggio che vedo su questa soluzione rispetto a quello di avere un singolo metodo chiamato da due test è che non devo ripetere il codice per impostare la corretta implementazione. In questa soluzione hai una singola linea che chiama base.TestName(), e non due righe, una per impostare il Searcher e un'altra per chiamare il test. Visual Studio rende anche la scrittura molto più veloce ... digito semplicemente "override" e ottengo un elenco di scelte. Il completamento automatico scrive il resto per me.

0

Chiarimenti basati sui test effettuati.

La risposta accettata (per utilizzare una classe astratta) funziona fino a la classe astratta e le classi concrete si trovano nello stesso assieme.

Se si desidera avere classi astratte e classi concrete in diverse assemblee, l'approccio menzionato da KarlZ sembra sfortunatamente necessario. Non sono sicuro del motivo per cui questo è il caso. In questo scenario, TestExplorer non mostrerà TestMethod.

Inoltre, la risposta accettata utilizza classi concrete annidate all'interno della classe astratta. Questo non sembra essere un requisito.

Test con MSTestV2 (1.1.17), VS2017. Ecco le classi di esempio utilizzate.

Assembly 1 
    [TestClass] 
    public abstract class SampleExternal 
    { 
     [TestMethod] 
     public void SampleTest01() 
     { 
      Assert.IsTrue(false, this.GetType().Name); 
     } 
    } 

Assembly 2 
    [TestClass] 
    public abstract class Sample 
    { 
     [TestMethod] 
     public void SampleTest01() 
     { 
      Assert.IsTrue(false, this.GetType().Name); 
     } 

     [TestClass] 
     public class SampleA : Sample 
     { 
     } 
    } 

    [TestClass] 
    public class SampleB : Sample 
    { 
    } 

    [TestClass] 
    public class SampleC : SampleExternal 
    { 
    } 

    [TestClass] 
    public class SampleD : SampleExternal 
    { 
    } 

utilizzo di questi, il test per SampleA e SampleB eseguirà (e cedere per il design), ma SampleC & campionati non lo faranno.

Problemi correlati