I metodi di prova di grandi dimensioni indicano un odore di codice?

5

Ho un particolare metodo chiamato TranslateValues () (Cyclomatic-Complexity of 5) che vorrei testare.

Il test richiede un numero considerevole di oggetti finti che occupano la maggior parte del metodo ; Il metodo testato è abbastanza semplice con questo requisito eccezionale. Sono sospettoso di questo. Per me, potrei fare qualcosa di simile per alleviare il problema:

namespace TestArea
{
    [TestClass]
    public class PrimaryTests
    {
        //commonly used classes should go up here
        private CoreServiceClient client;
        public CoreServiceClient Client
        {
            get { return client; }
            set { client = value; }
        }

        [TestMethod]
        public void TestMethodForXYZ()
        {
            try
            {
              //use the Client, do some stuff, and assert
            }
            catch
            {
             //catch any problems, report them, and assert.fail
            }
        }
   }
}

Ma questo ti porterà solo a ridurre le dimensioni e il disordine dei metodi di test, e quindi solleva la domanda: I grandi metodi di test sono generalmente indicativi dell'odore del codice? Come valuti quando i tuoi metodi stanno diventando troppo grandi?

    
posta Ray 26.07.2012 - 00:03
fonte

4 risposte

2

Dico di si. Stai provando troppo.

Ne sono colpevole anch'io ma idealmente si prova una cosa alla volta (da leggere come "una asserzione per test"). Questa è la visione utopica e fintanto che sei più vicino a questo rispetto al "one test per tutto con un sacco di assert" lato sei ok.

Penso che dipenda davvero dall'uso corretto dello strumento. NUnit, ad esempio, ha TestCaseAttributes in modo che tu possa avere un test che viene eseguito in diversi casi, che hai sostanzialmente descritto. Eseguirli tutti attraverso un test è meno utile perché dovrebbe fermarsi al primo errore in modo da poter solo dire se si sono rotti più casi risolvendo i singoli errori così come li vedi.

Per quanto riguarda la misura quando sono troppo grandi, è più o meno quando voglio avere una specifica sessione Assert (e non altre) ma non posso perché il test deve eseguire diversi altri Assert per rimanere valido.

Alla fine, significa davvero seguire la Responsabilità Unica durante la scrittura di test e il codice.

    
risposta data 26.07.2012 - 00:17
fonte
2

Perché il tuo test potrebbe essere lungo:

  • stai testando più di una cosa
  • le dipendenze di sut richiedono molta derisione (probabilmente il tuo caso)
  • il tuo sut richiede molte dipendenze

Se stai testando più di una cosa

Scrivi test separati, per testare ogni cosa. Ogni test richiederà meno organizzazione e meno asserzioni. Quando alcuni test verranno interrotti, saprai esattamente cosa non funziona come previsto.

Se i tuoi mock richiedono molte impostazioni

Prima di tutto, dovresti verificare perché il tuo istante chiede troppo la sua dipendenza. Forse prendi decisioni in base allo stato di un altro oggetto, violando quindi il principio Tell, Do not Ask . Prendi in considerazione di spostare la logica delle decisioni in dipendenza.

// for this code you should setup mocks of person and address
if (person.Address != null) {
  Address address = person.Address;
  address.City = city;
}

vs

person.RelocateTo(city);

Se sei completamente sicuro, che tutta la logica è nella sua posizione, ma la tua simulazione richiede ancora qualche grande installazione, quindi considera di spostare la creazione fittizia in un metodo separato. Ciò consentirà anche di riutilizzare tale metodo in diversi test.

Mock<Foo> fooMock = new Mock<Foo>();
fooMock.Setup(foo => foo.X).Returns("x");
fooMock.Setup(foo => foo.Y).Returns("y");
fooMock.Setup(foo => foo.Z).Returns("z");
Bar bar = new Bar(fooMock.Object);

vs

Foo foo = CreateFoo("x", "y", "z");
Bar bar = new Bar(foo);

Se il tuo sut richiede molte dipendenze

Se alcune delle tue dipendenze vengono sempre utilizzate insieme, piuttosto che prendere in considerazione l'introduzione di un oggetto, che le manterrà. Verifica anche se hai davvero bisogno di passare l'intero oggetto. Forse hai solo bisogno di un campo da quell'oggetto.

public void Foo(TimeProvider timeProvider)
{
   DateTime time = timeProvider.GetCurrentTime();
   ...
}

vs

public void Foo(DateTime currentTime)
{
   ...
}
    
risposta data 26.07.2012 - 09:28
fonte
1

Non credo che un metodo di prova grande sia l'odore del codice da solo.

Tuttavia, ci sono segni, per esempio quando

  • ha bisogno di prendere in giro troppe dipendenze (anche troppo è relativo)
  • necessità di impostare troppe cose
  • ha bisogno di sapere troppe cose sui reali dettagli di implementazione (quando fai TDD non sarà un problema)
  • non può separare il test di un metodo in casi di test ben definiti (metodi di test diversi)

Dalla mia esperienza, uno dei più grandi segnali che un metodo sta facendo è quando non puoi scrivere un caso di test specifico, solo qualcosa come TestOfMyMethod() . Questo probabilmente non è un caso di test, ma solo una grande quantità di codice che copre l'esatta implementazione del metodo attuale, che si interromperà la prossima volta che cambierai qualcosa.

    
risposta data 26.07.2012 - 00:19
fonte
1

No , non sempre. I Grandi metodi di test sono spesso i risultati di test / strutture di simulazione e il loro funzionamento (o, precisamente, il modo in cui dovresti usarli).

Ad esempio, il metodo potrebbe essere perfettamente raffinato e ben progettato, ma immaginiamo che utilizzi 2 o 3 dipendenze: per testarlo, probabilmente dovrai stubare / deridere quelle dipendenze. Questo aggiunge ulteriori linee di codice per testare il metodo, che non sembra affatto di attribuire a un metodo testato.

Detto questo, le dipendenze troppo potrebbero essere un buon indicatore (il che, inoltre, comporterà un codice più lungo per il metodo di prova a causa di un lavoro di installazione extra) ed è lì che starei prudente, non la lunghezza si.

Esempio molto banale di test abbastanza comune, in cui il metodo di test sarà "long" , rispetto a quello che sta effettivamente testando (che probabilmente è una riga di codice) :

[Test]
public void GenerateReport_ThrowsInvalidOperationException_WhenTaskIsNotReady()
{
    var providerMock = new Mock<ITaskProvider>();
    providerMock.Setup(m => m.IsTaskReady).Returns(false);
    var sut = new ReportGenerator(providerMock);

    Assert.That(sut, Throws.InstanceOf<InvalidOperationException>());
}

Ora, aggiungi un po 'più complesso% di chiamata di.Setup, un po' di asserzione più complessa - questo si traduce in una dimensione ancora maggiore del metodo di test.

    
risposta data 26.07.2012 - 00:13
fonte

Leggi altre domande sui tag