È una cattiva pratica, se devo fingere qualcosa che non è rilevante per il mio test?

5

Ho un metodo chiamato Buy , quando chiamato lo fa quanto segue:

  • Chiama una fabbrica per creare un ordine
  • Le chiamate pagano su un oggetto di pagamento
  • Chiamate vuote sul carrello del cliente
  • Chiama il dao per salvare l'ordine

Questo è ciò che il mio diagramma di sequenza dice che dovrebbe accadere, tuttavia, quando scrivo il mio unit test per verificare che chiami Empty sul carrello, fallisce (puntatori nulli) a meno che non specifichi che la fabbrica restituisce un ordine.

Quindi finisco per simulare un comportamento che non è rilevante, solo per verificare se Empty è stato chiamato sul carrello.

È un odore riconosciuto dal codice? Quando lo riconosci nel tuo codice, lo cambi? Potrei ovviamente dividere la funzionalità del carrello vuoto, ma mi piace l'API pulita di Buy . Per non parlare del fatto che suddividerlo richiede Empty per essere chiamato prima o l'ultima, che in questo caso non è un problema, ma in alcune situazioni potrebbe essere.

Questo fa un po 'male ai miei test, e puzza decisamente come se il mio metodo facesse più del dovuto, quindi qualsiasi suggerimento per pulire le soluzioni sarebbe apprezzato.

//Implementation
public void Buy()
{
    var order = CreateOrder();
    Pay();
    EmptyCart();
    SaveChanges(order);
}

//Test
[TestMethod]
public void Buy_WhenCalled_CallsEmptyOnCart()
{
    var cart = CreateFakeCartThatExpectsCallToEmpty();
    var order = CreateFakeOrder().Object;
    var orderFactory = CreateFakeOrderFactoryThatReturnsOrder(order).Object;
    Cashier cashier = new Cashier(orderFactory, cart);

    cashier.Buy();

    cart.Verify();
}
    
posta Chris Wohlert 26.08.2016 - 09:01
fonte

3 risposte

7

No non è un odore di codice. Solo se hai bisogno di falsificare dipendenze che non sono correlate al comportamento specificato del metodo sotto test, allora sarebbe un odore di codice. Ma fa parte del comportamento specificato che il metodo Buy() -method consente di creare un ordine, quindi ci si aspetterebbe di fornire un ordine reale o un falso.

In realtà se il metodo non ha fallito nel ricevere un null invece di un ordine, potrebbe essere considerato un odore di codice.

Capisco che tu voglia solo test una particolare parte del comportamento dei metodi, ma dovresti comunque aspettarti che il metodo faccia tutto ciò che deve fare.

    
risposta data 26.08.2016 - 09:42
fonte
3

Sì. A mio parere, sembra esserci un problema con l'accoppiamento. Ad esempio, la creazione dell'ordine nel metodo Buy sembra violare il principio di responsabilità singola, così come la chiamata a SaveChanges . La ragione di questo, mi sembra, è che

Risolverei questo problema con una combinazione di eventi / delega e iniezione di oggetti (dipendenza).

Eventi

Ciò consente una soluzione piuttosto pulita e una che preferisco usare in un sistema orientato agli oggetti. Attivando specifici eventi durante il processo, puoi configurare gli ascoltatori in un modo ben disaccoppiato (ad esempio, l'oggetto Cart ascolta l'evento PurchaseProcessComplete e poi si svuota) e ti consente di aggiungere / rimuovere molto semplicemente comportamenti basati sugli eventi che si attivano. Puoi anche creare questi eventi e inserirli con le informazioni aggiuntive di cui hai bisogno, il che può essere un bonus (ad esempio, un numero di ordine di acquisto che puoi visualizzare sull'interfaccia utente).

In questo caso, il test diventa una questione di controllare se gli eventi appropriati sono licenziati (tramite l'uguaglianza). Inoltre, puoi attivare gli eventi durante le varie fasi del processo per garantire che le cose accadano in un dato ordine.

Iniezione oggetto

Questo è importante per evitare dipendenze non necessarie. Senza ulteriore conoscenza del tuo sistema, non posso fornire dettagli migliori, ma in genere, Cart e Order vengono creati prima del processo Checkout . Quindi, passali come argomenti. Il Cashier non sembra, intuitivamente, il posto giusto per creare ordini (e sembra anche strano che OrderFactory richieda il Order ... non dovrebbe un OrderFactory creare Order s?). Forse Cashier dovrebbe esistere indipendentemente da Cart e Order , e dovrebbero essere passati a Buy . Forse ciò di cui hai bisogno è un oggetto CheckoutProcess , che prende Cart e Order .

In sintesi, ci sono molti modi in cui questo potrebbe andare - e dipende da te decidere quale usare. Consiglierei di utilizzare un modo più indiretto per controllare il processo, in modo da poter disporre di oggetti e metodi più semplici con responsabilità singole. Come sempre, YMMV.

Buona fortuna!

    
risposta data 26.08.2016 - 21:23
fonte
1

Sembra che il tuo problema sia che devi scrivere molto codice di scaffolding che non ha a che fare con il tuo test a portata di mano. Questo è l'odore che stai rilevando.

Il tuo problema è un comune attrito nei test unitari. Ti suggerisco di selezionare XUnit Test Patterns , che può fornire alcuni strumenti avanzati per il test delle unità. In particolare, esaminerei il modello Oggetto Madre , che fornirà oggetti coerenti in modo da non dover scrivere ripetutamente codice che non ha a che fare con il tuo test a portata di mano.

    
risposta data 26.08.2016 - 21:51
fonte

Leggi altre domande sui tag