NUnit - jak przetestować wszystkie klasy implementujące dany interfejs

Jeśli mam interfejs IFoo i kilka klas, które go implementują, jaki jest najlepszy / najbardziej elegancki/najmądrzejszy sposób przetestowania wszystkich tych klas pod kątem interfejsu?

Chciałbym ograniczyć powielanie kodu testowego, ale nadal "pozostań wierny" zasadom testowania jednostkowego.

Co uznasz za najlepszą praktykę? Używam NUnit, ale przypuszczam, że przykłady z dowolnego frameworka do testowania jednostek byłyby poprawne

Author: Frep D-Oronge, 2008-09-02

7 answers

Jeśli masz klasy implementujące jeden interfejs, to wszystkie muszą zaimplementować metody w tym interfejsie. Aby przetestować te klasy, musisz utworzyć klasę testu jednostkowego dla każdej z klas.

Jeśli twoim celem jest unikanie powielania kodu i testowanie kodu, możesz utworzyć abstrakcyjną klasę, która obsługuje powtarzający się kod.

Np. masz następujący interfejs:

public interface IFoo {

    public void CommonCode();

    public void SpecificCode();

}

Ty może warto utworzyć klasę abstrakcyjną:

public abstract class AbstractFoo : IFoo {

    public void CommonCode() {
          SpecificCode();
    }

    public abstract void SpecificCode();

}

Testowanie, które jest łatwe; zaimplementuj klasę abstrakcyjną w klasie testowej jako klasę wewnętrzną:

[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());
    }
}

...albo pozwól klasie testowej rozszerzyć samą klasę abstrakcyjną, jeśli to pasuje do twojej fantazji.

[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());
    }        

}

Posiadanie klasy abstrakcyjnej dba o wspólny kod, który implikuje interfejs, daje znacznie czystszy projekt kodu.

Mam nadzieję, że to ma dla ciebie sens.

Na marginesie, jest to wspólny wzorzec projektowy zwany na wzór metody szablonowej. W powyższym przykładzie metoda szablonowa jest metodą CommonCode i SpecificCode jest nazywana stubem lub Hookiem. Chodzi o to, że każdy może rozszerzyć zachowanie bez potrzeby poznania rzeczy zza kulis.

Wiele frameworków opiera się na tym wzorcu behawioralnym, np. ASP.NET gdzie musisz zaimplementować Hooki na stronie lub kontrolkach użytkownika, takich jak wygenerowana metoda Page_Load, która jest wywoływana przez zdarzenie Load, metoda szablonu nazywa haki za kulisami. Jest tego o wiele więcej przykładów. Zasadniczo wszystko, co musisz zaimplementować, używając słów "load", "init" lub "render", jest wywoływane przez metodę szablonu.

 13
Author: Spoike,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-08-02 14:21:46

Nie zgadzam się z Jon Limjap Kiedy mówi,

Nie jest to umowa na Ani a.) jak metoda powinna być wdrożona i b.) co ta metoda powinna robić dokładnie (gwarantuje tylko Typ powrotu), dwa powody, które zbieram byłyby motywacją W chce tego rodzaju testu.

Może być wiele części Umowy, które nie zostały określone w rodzaju zwrotu. Przykład językowo-agnostyczny:

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);

}

Twoje testy jednostkowe na LinkedList, ArrayList, CircularlyLinkedList, a wszystkie pozostałe powinny sprawdzić nie tylko, czy same listy są zwracane, ale także czy zostały odpowiednio zmodyfikowane.

Było wcześniejsze pytanie dotyczące projektu według umowy, które może pomóc wskazać właściwy kierunek na jeden ze sposobów suszenia tych testów.

Jeśli nie chcesz narzutu kontraktów, polecam platformy testowe, według tego, co Spoike zalecane:

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
  }
}
 11
Author: James A. Rosen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:18:27

To nie jest najlepsza praktyka.

Prosta prawda jest taka, że interfejs jest niczym więcej niż kontrakt, że metoda jest zaimplementowana. Jest to nie kontrakt na obu.) jak metoda powinna być wdrożona i b.) co ta metoda powinna robić dokładnie (gwarantuje tylko Typ powrotu), dwa powody, które zbieram byłyby motywacją W chce tego rodzaju testu.

Jeśli naprawdę chcesz mieć kontrolę nad implementacją metody, masz wariant:

  • Implementowanie go jako metody w klasie abstrakcyjnej i dziedziczenie z niej. Nadal będziesz musiał dziedziczyć ją do konkretnej klasy, ale jesteś pewien, że jeśli nie jest to jawnie przesłonięte, ta metoda zrobi to poprawnie.
  • W 2009 roku, w ramach projektu, w ramach projektu, została uruchomiona nowa wersja platformy. net. net.]}

Przykład:

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

Każda implementacja odpowiednio odwołująca się do tej metody rozszerzenia będzie emitować dokładnie to metoda rozszerzenia, więc wystarczy ją przetestować tylko raz.

 3
Author: Jon Limjap,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-02 08:41:32

@Emperor XLII

Podoba mi się dźwięk testów kombinatorycznych w MbUnit, wypróbowałem technikę abstrakcyjnego testu interfejsu klasy bazowej z NUnit, i chociaż to działa, musisz mieć oddzielną oprawę testową dla każdego interfejsu implementowanego przez klasę (ponieważ w C# nie ma wielokrotnego dziedziczenia - chociaż można używać klas wewnętrznych, co jest całkiem fajne). W rzeczywistości jest to w porządku, może nawet korzystne, ponieważ grupuje testy dla klasy implementującej według interfejsu. Ale to byłoby dobrze, gdyby ramy były mądrzejsze. Gdybym mógł użyć atrybutu, aby oznaczyć klasę jako' oficjalną ' klasę testową dla interfejsu, a framework przeszukałby testowany zestaw pod kątem wszystkich klas implementujących interfejs i uruchomiłby na nim te testy.

Byłoby fajnie.

 1
Author: Frep D-Oronge,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-05 15:36:37

A może hierarchia klas [TestFixture]s? Umieść kod testu publicznego w klasie testu podstawowego i Dziedzicz go w klasach testu dziecięcego..

 1
Author: Andrei Rînea,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-14 19:01:34

Podczas testowania kontraktu z interfejsem lub klasą bazową, wolę pozwolić frameworkowi testowemu automatycznie zająć się znalezieniem wszystkich implementatorów. Pozwala to skoncentrować się na testowanym interfejsie i mieć pewność, że wszystkie implementacje zostaną przetestowane, bez konieczności wykonywania wielu ręcznych implementacji.

  • dla xUnit.net, stworzyłem bibliotekę Type Resolver do wyszukiwania wszystkich implementacji danego typu (xUnit.net rozszerzenia to tylko cienkie opakowanie nad funkcjonalnością typu Resolver, dzięki czemu można go dostosować do użytku w innych frameworkach).
  • W MbUnit , możesz użyć CombinatorialTest z atrybutami UsingImplementations na parametrach.
  • W przypadku innych frameworków użyteczny może być wspomniany wzorzec klasy bazowej Spoike .

Poza testowaniem podstaw interfejsu, powinieneś również sprawdzić, czy każda indywidualna implementacja spełnia swoje specyficzne wymagania.

 1
Author: Emperor XLII,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 10:29:46

Nie używam NUnit, ale przetestowałem interfejsy C++. Chciałbym najpierw przetestować klasę TestFoo, która jest podstawową implementacją, aby upewnić się, że ogólne rzeczy działają. Następnie wystarczy przetestować rzeczy, które są unikalne dla każdego interfejsu.

 0
Author: graham.reeds,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-02 08:16:43