È corretto introdurre metodi che vengono utilizzati solo durante i test unitari?

12

Recentemente ero TDDing un metodo di fabbrica. Il metodo consisteva nel creare un oggetto semplice o un oggetto avvolto in un decoratore. L'oggetto decorato potrebbe essere di uno dei tanti tipi che estendono StrategyClass.

Nel mio test volevo controllare, se la classe dell'oggetto restituito è come previsto. È semplice quando viene restituito il semplice oggetto os, ma cosa fare quando è racchiuso all'interno di un decoratore?

Codice in PHP, quindi potrei usare ext/Reflection per trovare una classe di oggetto spostato, ma mi sembrava che facesse da complemento alle cose, e in qualche modo le regole di nuovo del TDD.

Invece ho deciso di introdurre getClassName() che restituirebbe il nome della classe dell'oggetto quando chiamato da StrategyClass. Tuttavia, quando chiamato dal decoratore, restituisce il valore restituito con lo stesso metodo in oggetto decorato.

Un codice per renderlo più chiaro:

interface StrategyInterface {
  public function getClassName();
}

abstract class StrategyClass implements StrategyInterface {
  public function getClassName() {
    return \get_class($this);
  }
}

abstract class StrategyDecorator implements StrategyInterface {

  private $decorated;

  public function __construct(StrategyClass $decorated) {
    $this->decorated = $decorated;
  }

  public function getClassName() {
    return $this->decorated->getClassName();
  }

}

E un test PHPUnit

 /**
   * @dataProvider providerForTestGetStrategy
   * @param array $arguments
   * @param string $expected
   */
  public function testGetStrategy($arguments, $expected) {

    $this->assertEquals(
      __NAMESPACE__.'\'.$expected,  
      $this->object->getStrategy($arguments)->getClassName()
    )
  }

//below there's another test to check if proper decorator is being used

Il mio punto qui è: è corretto introdurre tali metodi, che non hanno altro scopo se non quello di facilitare i test unitari? In qualche modo non mi sembra giusto.

    
posta Mchl 24.06.2011 - 21:25
fonte

4 risposte

13

Il mio pensiero è no, non dovresti fare nulla solo perché è necessario per testabilità. Molte decisioni prese dalle persone hanno beneficio per la testabilità e la testabilità potrebbe essere il principale vantaggio, ma dovrebbe essere una buona decisione di progettazione su altri meriti. Ciò significa che alcune proprietà desiderate non sono verificabili. Un altro esempio è quando hai bisogno di sapere quanto sia efficiente qualche routine, per esempio il tuo Hashmap usa un intervallo di valori hash distribuito in modo uniforme - nulla nell'interfaccia esterna sarebbe in grado di dirti questo.

Invece di pensare, "sto ottenendo la giusta strategia di classe", pensa "la classe che ottengo esegue ciò che questa specifica sta cercando di testare?" È bello quando puoi testare l'impianto idraulico interno ma non devi farlo, basta provare a girare la manopola e vedere se ottieni acqua calda o fredda.

    
risposta data 24.06.2011 - 21:51
fonte
5

La mia opinione su questo è - a volte devi rielaborare il codice sorgente un po 'per renderlo più testabile. Non è l'ideale e non dovrebbe essere una scusa per ingombrare l'interfaccia con funzioni che dovrebbero essere usate solo per i test, quindi la moderazione è generalmente la chiave qui. Inoltre, non vuoi essere nella situazione in cui gli utenti del tuo codice utilizzano improvvisamente le funzioni dell'interfaccia di test per le normali interazioni con il tuo oggetto.

Il mio modo preferito di gestirlo (e devo scusarmi che non riesco a mostrare come farlo in PHP poiché codifico principalmente in linguaggio in stile C) è quello di fornire le funzioni di 'test' in modo che non sono esposti al mondo esterno dall'oggetto stesso, ma possono essere accessibili dagli oggetti derivati. A scopo di test, vorrei quindi derivare una classe che gestisse l'interazione con l'oggetto che in realtà voglio testare e che il test dell'unità usi quell'oggetto particolare. Un esempio in C ++ sarebbe simile a questo:

Classe del tipo di produzione:

class ProdObj
{
  ...
  protected:
     virtual bool checkCertainState();
};

Test helper class:

class TestHelperProdObj : public ProdObj
{
  ...
  public:
     virtual bool checkCertainState() { return ProdObj::checkCertainState(); }
};

In questo modo, almeno sei in una posizione in cui non devi esporre le funzioni di tipo "test" nell'oggetto principale.

    
risposta data 24.06.2011 - 21:57
fonte
3

Pochi mesi fa, quando ho messo la mia nuova lavastoviglie, ne è uscita molta acqua, ho capito che questo è probabilmente dovuto al fatto che è stato testato correttamente nella fabbrica da cui proveniva. Non è raro vedere fori di montaggio e cose su macchinari che sono lì solo per lo scopo di testare in una catena di montaggio.

Il test è importante, se necessario, basta aggiungere qualcosa per questo.

Ma prova alcune delle alternative. La tua opzione basata sulla riflessione non è poi così male. Potresti avere un accesso virtuale protetto a ciò di cui hai bisogno e creare una classe derivata da testare e affermare contro. Forse puoi dividere la tua classe e testare direttamente la classe foglia risultante. Anche nascondere il metodo di prova con una variabile del compilatore nel codice sorgente è un'opzione (conosco a malapena PHP, non sono sicuro che sia possibile in PHP).

Nel tuo contesto potresti decidere di non testare la composizione corretta all'interno del decoratore, ma testare il comportamento previsto che dovrebbe avere la decorazione. Questo forse metterebbe un po 'più l'attenzione sul comportamento previsto del sistema e non tanto sulle specifiche tecniche (cosa ti fa guadagnare dal punto di vista funzionale il modello di decoratore?).

    
risposta data 25.06.2011 - 02:14
fonte
1

Sono un novizio assoluto di TDD, ma sembra dipendere dal metodo che si sta aggiungendo. Da quanto ho capito di TDD, i tuoi test dovrebbero "guidare" la creazione della tua API in una certa misura.

Quando è OK:

  • Fintanto che il metodo non interrompe l'incapsulamento e serve uno scopo in linea con la responsabilità dell'oggetto.

Quando non è OK:

  • Se il metodo sembra qualcosa che non sarebbe mai utile o che non ha senso in relazione ad altri metodi di interfaccia, potrebbe essere incoerente o confuso. A quel punto, mi confonderebbe la mia comprensione di quell'oggetto.
  • L'esempio di Jeremy, "... quando hai bisogno di sapere quanto sia efficiente una routine, per esempio la tua Hashmap usa un intervallo di valori hash distribuito in modo uniforme - nulla nell'interfaccia esterna sarebbe in grado di dirti questo."
risposta data 14.02.2012 - 18:35
fonte

Leggi altre domande sui tag