Fare in modo che un dispositivo di prova erediti dalla classe SUT

2

Qualche tempo fa ho dovuto rivedere il codice di un apparecchio di test unitario come questo (codice C #):

public class ServiceFixture : Service
{
    [Fact]
    public void Test_DoSomething()
    {
        Assert(DoSomething(), SomeExpectedResult);
    }
}

Quindi Service è il SUT, la classe sotto test e ServiceFixture è il dispositivo di prova. E ServiceFixture eredita da Service .

Questo è, a proposito, un modo davvero non ortodosso per scrivere un dispositivo di prova (almeno in C #, Java, Scala, lingue che conosco di più). Di solito il SUT è istanziato dal dispositivo di test, equo come dice la mia esperienza ...

Ho avuto difficoltà a convincere l'autore che questo non dovrebbe essere fatto in questo modo però. Voglio dire, la ragione principale alla quale posso pensare è (oltre "non usuale") l'accoppiamento non necessario tra fixture e SUT (che è correlato al vecchio Discussione sull'ereditarietà e l'aggregazione ).

Che altro si potrebbe dire al riguardo? Ci sono argomenti per questo tipo di pratica, o possiamo essere d'accordo che dovrebbe essere evitato?

    
posta rsenna 23.02.2018 - 13:03
fonte

1 risposta

1

Questo è un modo problematico di scrivere test perché il test non specifica l'istanziazione del SUT. Il Fixture e implicitamente con esso il SUT saranno istanziati dal framework di test. Questo porta ai seguenti problemi:

  • Non puoi fornire argomenti del costruttore.
  • A seguito di ciò, non è possibile impostare grafici di oggetti complicati o eseguire un'iniezione di dipendenza manuale.
  • Questo porta a problemi se l'istanza SUT deve essere creata solo attraverso una fabbrica, o se deve essere registrata da qualche parte nell'applicazione per funzionare correttamente.
  • Lo stesso oggetto viene riutilizzato per tutti i casi di test (forse - dipende dal framework di testing).
  • Non puoi ripristinare lo stato SUT per il prossimo test a meno che non sia mutabile.

Per evitare tali problemi, ti consiglio di impostare SUT all'interno di ogni caso di test e di fare affidamento solo su dispositivi automatici se semplificano notevolmente i test.

Come già accennato, l'uso dell'ereditarietà stessa potrebbe essere problematico.

  • Il fragile problema della classe base: le modifiche benigne alla classe base potrebbero interrompere le classi derivate. C # rende questo difficile da fare, ma non è impossibile. In tutte le lingue: una classe deve essere progettata esplicitamente per ereditarietà se deve essere utilizzata come classe base.
  • Le aggiunte nella classe derivata potrebbero modificare accidentalmente il comportamento dell'oggetto. Ancora una volta, C # rende questo difficile ma non impossibile. Se il tuo servizio è soggetto a riflessione o può implementare interfacce opzionali, aggiunte come il fatto che il tuo dispositivo erediti da IDisposable potrebbero avere conseguenze indesiderate.

Ci sono casi in cui l'ereditarietà potrebbe essere la soluzione corretta: quando il SUT è previsto come una classe base e i test devono accedere ai membri protetti. Altrimenti, è meglio mantenere una certa distanza tra il codice di produzione e i test.

Inoltre, questo è un modo non ovvio e inutilmente intelligente per scrivere test. Le sorprese sono cattive. Come discusso in Come sapresti se hai scritto un codice leggibile e facilmente gestibile? , ci sono un paio di indicatori questo approccio è buono o cattivo. Un estratto:

  • Your peer tells you after reviewing the code.

    Hai detto al tuo collega che questo potrebbe non essere l'ideale, il che è un buon indicatore che non lo è.

  • It is:

    1. maintainable if you can maintain it.
    2. easily maintainable if someone else can maintain it without asking you for help
    3. readable if someone else, on reading it, correctly understands the design, layout and intent

    Non capisco perché il test abbia sia scritto in questo modo non ortodosso. Sicuramente c'era una ragione speciale per questo? Dovrei chiedere.

  • Watch what happens after handing it off to a reasonably competent person. If they don't need to ask many questions relative to the difficulty of the code, you've done a good job.

    Questo è un codice molto semplice, ma solleva delle domande.

Se si lascia questo codice non necessario nella base di codice, si sta anche impostando un cattivo esempio. Di per sé, questa eredità non è veramente dannosa. Ma in altri casi, i suddetti inconvenienti potrebbero influire sulla validità dei test.

    
risposta data 24.02.2018 - 12:51
fonte

Leggi altre domande sui tag