Domanda

Se ho l'interfaccia IFoo e ho diverse classi che la implementano, qual è il modo migliore/più elegante/più intelligente per testare tutte quelle classi rispetto all'interfaccia?

Vorrei ridurre la duplicazione del codice di test, ma comunque "rimanere fedele" ai principi del test unitario.

Cosa considereresti la migliore pratica?Sto usando NUnit, ma suppongo che gli esempi di qualsiasi framework di test unitario sarebbero validi

È stato utile?

Soluzione

Se le classi implementano una qualsiasi interfaccia, tutte devono implementare i metodi in quell'interfaccia.Per testare queste classi è necessario creare una classe di test unitario per ciascuna delle classi.

Scegliamo invece un percorso più intelligente;se il tuo obiettivo è quello evitare il codice e testare la duplicazione del codice potresti invece voler creare una classe astratta che gestisca il file ricorrente codice.

Per esempio.hai la seguente interfaccia:

public interface IFoo {

    public void CommonCode();

    public void SpecificCode();

}

Potresti voler creare una classe astratta:

public abstract class AbstractFoo : IFoo {

    public void CommonCode() {
          SpecificCode();
    }

    public abstract void SpecificCode();

}

Testarlo è facile;implementare la classe astratta nella classe test come classe interna:

[TestFixture]
public void TestClass {

    private class TestFoo : AbstractFoo {
        boolean hasCalledSpecificCode = false;
        public void SpecificCode() {
            hasCalledSpecificCode = true;
        }
    }

    [Test]
    public void testCommonCallsSpecificCode() {
        TestFoo fooFighter = new TestFoo();
        fooFighter.CommonCode();
        Assert.That(fooFighter.hasCalledSpecificCode, Is.True());
    }
}

...o lascia che la classe test estenda la classe astratta stessa se questo si adatta alla tua fantasia.

[TestFixture]
public void TestClass : AbstractFoo {

    boolean hasCalledSpecificCode;
    public void specificCode() {
        hasCalledSpecificCode = true;
    }

    [Test]
    public void testCommonCallsSpecificCode() {
        AbstractFoo fooFighter = this;
        hasCalledSpecificCode = false;
        fooFighter.CommonCode();
        Assert.That(fooFighter.hasCalledSpecificCode, Is.True());
    }        

}

Avere una classe astratta che si occupa del codice comune implicato da un'interfaccia offre una progettazione del codice molto più pulita.

Spero che per te abbia senso.


Come nota a margine, questo è un modello di progettazione comune chiamato the Modello del metodo modello.Nell'esempio precedente, il metodo template è the CommonCode metodo e SpecificCode è chiamato stub o hook.L'idea è che chiunque possa estendere il comportamento senza la necessità di conoscere le cose dietro le quinte.

Molti framework si basano su questo modello comportamentale, ad es. ASP.NET dove devi implementare gli hook in una pagina o controlli utente come quelli generati Page_Load metodo che viene chiamato da Load evento, il metodo template chiama gli hook dietro le quinte.Ci sono molti altri esempi di questo.Fondamentalmente tutto ciò che devi implementare che utilizza le parole "load", "init" o "render" viene chiamato da un metodo template.

Altri suggerimenti

Non sono d'accordo Jon Limjap quando dice,

Non è un contratto su a.) come dovrebbe essere implementato il metodo e b.) cosa dovrebbe fare esattamente quel metodo (garantisce solo il tipo di ritorno), i due motivi che raccolgo sarebbero il motivo per cui desideri questo tipo di prova.

Potrebbero esserci molte parti del contratto non specificate nella tipologia di reso.Un esempio indipendente dalla lingua:

public interface List {

  // adds o and returns the list
  public List add(Object o);

  // removed the first occurrence of o and returns the list
  public List remove(Object o);

}

I test unitari su LinkedList, ArrayList, CircularlyLinkedList e tutti gli altri dovrebbero verificare non solo che gli elenchi stessi vengano restituiti, ma anche che siano stati modificati correttamente.

C'era un domanda precedente sulla progettazione per contratto, che può aiutarti a indirizzarti nella giusta direzione su un modo per seccare questi test.

Se non vuoi il sovraccarico dei contratti, ti consiglio banchi di prova, sulla falsariga di cosa Spoike consigliato:

abstract class BaseListTest {

  abstract public List newListInstance();

  public void testAddToList() {
    // do some adding tests
  }

  public void testRemoveFromList() {
    // do some removing tests
  }

}

class ArrayListTest < BaseListTest {
  List newListInstance() { new ArrayList(); }

  public void arrayListSpecificTest1() {
    // test something about ArrayLists beyond the List requirements
  }
}

Non penso che questa sia la pratica migliore.

La semplice verità è che un'interfaccia non è altro che un contratto per l'implementazione di un metodo.È non un contratto su a.) come dovrebbe essere implementato il metodo e b.) cosa dovrebbe fare esattamente quel metodo (garantisce solo il tipo restituito), i due motivi che raccolgo sarebbero il motivo per cui desideri questo tipo di test.

Se vuoi davvero avere il controllo sull'implementazione del tuo metodo, hai la possibilità di:

  • Implementarlo come metodo in una classe astratta ed ereditarlo.Dovrai comunque ereditarlo in una classe concreta, ma sei sicuro che, a meno che non venga esplicitamente sovrascritto, quel metodo farà la cosa corretta.
  • In .NET 3.5/C# 3.0, l'implementazione del metodo come metodo di estensione che fa riferimento all'interfaccia

Esempio:

public static ReturnType MethodName (this IMyinterface myImplementation, SomeObject someParameter)
{
    //method body goes here
}

Qualsiasi implementazione che faccia correttamente riferimento a quel metodo di estensione genererà esattamente quel metodo di estensione, quindi dovrai testarlo solo una volta.

@Imperatore XLII

Mi piace il suono dei test combinatori in MbUnit, ho provato la tecnica di test astratta dell'interfaccia della classe base con NUnit e, sebbene funzioni, dovresti avere un dispositivo di test separato per ogni interfaccia implementata da una classe (poiché in C# non esiste ereditarietà multipla, anche se è possibile utilizzare le classi interne, il che è piuttosto interessante).In realtà questo va bene, forse anche vantaggioso perché raggruppa i test per la classe di implementazione per interfaccia.Ma sarebbe positivo se il quadro potesse essere più intelligente.Se potessi usare un attributo per contrassegnare una classe come classe di test "ufficiale" per un'interfaccia, e il framework cercherebbe nell'assembly sotto test tutte le classi che implementano l'interfaccia ed eseguirebbe tali test su di essa.

Quello dovrebbe essere bello.

Che ne dici di una gerarchia delle classi di [TestFixture]?Inserisci il codice di test comune nella classe di test base ed ereditalo nelle classi di test figlie.

Quando si testa un'interfaccia o un contratto di classe base, preferisco lasciare che il framework di test si occupi automaticamente di trovare tutti gli implementatori.Ciò ti consente di concentrarti sull'interfaccia sotto test ed essere ragionevolmente sicuro che tutte le implementazioni verranno testate, senza dover eseguire molte implementazioni manuali.

  • Per xUnit.net, ho creato un Digitare Risolutore libreria per cercare tutte le implementazioni di un particolare tipo (le estensioni xUnit.net sono solo un sottile wrapper sulla funzionalità Type Resolver, quindi può essere adattata per l'uso in altri framework).
  • In MbUnit, puoi usare a CombinatorialTest con UsingImplementations attributi sui parametri.
  • Per altri framework, il modello della classe base Spoike menzionato può essere utile.

Oltre a testare le basi dell'interfaccia, dovresti anche testare che ogni singola implementazione segua i suoi requisiti particolari.

Non uso NUnit ma ho testato le interfacce C++.Vorrei prima testare una classe TestFoo che ne è un'implementazione di base per assicurarmi che le cose generiche funzionino.Quindi devi solo testare le cose uniche per ciascuna interfaccia.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top