Testowanie i sprawdzanie wartości zmiennej prywatnej

Piszę testy jednostkowe z C#, NUnit i Rhino. Oto odpowiednie części klasy, którą testuję:

public class ClassToBeTested
{
    private IList<object> insertItems = new List<object>();

    public bool OnSave(object entity, object id)
    {
        var auditable = entity as IAuditable;
        if (auditable != null) insertItems.Add(entity);

        return false;            
    }
}

Chcę przetestować wartości w insertItems po wywołaniu OnSave:

[Test]
public void OnSave_Adds_Object_To_InsertItems_Array()
{
     Setup();

     myClassToBeTested.OnSave(auditableObject, null);

     // Check auditableObject has been added to insertItems array            
}

Jaka jest najlepsza praktyka w tym zakresie? Rozważałem dodanie insertItems jako właściwości z publicznym getem lub wstrzyknięcie Listy Do ClassToBeTested, ale nie jestem pewien, czy powinienem modyfikować kod do celów testowych.

Przeczytałem wiele postów na temat testowania prywatnych metod i refaktoryzacji, ale to taka prosta klasa, zastanawiałem się, co jest najlepszą opcją.

Author: Ruben Bartelink, 2009-07-07

6 answers

Szybka Odpowiedź jest taka, że nigdy, przenigdy nie powinieneś uzyskiwać dostępu do niepublicznych członków z testów jednostkowych. Całkowicie przeciwstawia się celowi posiadania pakietu testowego, ponieważ blokuje cię w wewnętrznych szczegółach implementacji, których możesz nie chcieć zachować w ten sposób.

Dłuższa odpowiedź odnosi się do tego, co wtedy zrobić? W tym przypadku ważne jest, aby zrozumieć, dlaczego implementacja jest taka, jaka jest (dlatego TDD jest tak potężny, ponieważ używamy testów do określić oczekiwane zachowanie, ale masz wrażenie, że nie używasz TDD).

W Twoim przypadku pierwsze pytanie, które przychodzi mi do głowy, brzmi: "dlaczego obiekty IAuditable są dodawane do wewnętrznej listy?"lub, inaczej mówiąc," jaki jest oczekiwany zewnętrznie widoczny wynik tej realizacji?"W zależności od odpowiedzi na te pytania, to jest to, co musisz przetestować.

Jeśli dodasz obiekty IAuditable do swojej listy wewnętrznej, ponieważ później chcesz je zapisać do dziennika audytu (po prostu dzika guess), a następnie wywołać metodę, która zapisuje log i sprawdzić, czy oczekiwane dane zostały zapisane.

Jeśli dodasz obiekt IAuditable do swojej listy wewnętrznej, ponieważ chcesz zgromadzić dowody przeciwko jakiemuś późniejszemu ograniczeniu, spróbuj to przetestować.

Jeśli dodałeś kod bez wymiernego powodu, to usuń go ponownie:)

Ważne jest to, że bardzo korzystne jest testowanie zachowania zamiast implementacji. Jest również bardziej wytrzymały i możliwa do utrzymania forma testowania.

Nie bój się modyfikować testowanego systemu (SUT), aby był bardziej testowalny. Tak długo, jak twoje dodatki mają sens w Twojej domenie i postępują zgodnie z najlepszymi praktykami obiektowymi, nie ma żadnych problemów - po prostu przestrzegałbyś zasady otwartej / zamkniętej.

 74
Author: Mark Seemann,
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
2011-10-07 17:59:21

Nie powinieneś sprawdzać listy, na której został dodany element. Jeśli to zrobisz, napiszesz test jednostkowy dla metody Add z listy, a nie test dla twojego kodu. Po prostu sprawdź wartość zwrotu OnSave; to naprawdę wszystko, co chcesz przetestować.

Jeśli naprawdę martwisz się dodaniem, wykpij to z równania.

Edit:

@TonE: po przeczytaniu twoich komentarzy powiedziałbym, że możesz zmienić swoją obecną metodę OnSave, aby poinformować Cię o awariach. Możesz wybrać, aby rzucić wyjątek, jeśli obsada nie powiedzie, itp. Można wtedy napisać test jednostkowy, który oczekuje i wyjątek, a taki, który nie.

 6
Author: Esteban Araya,
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
2009-07-07 16:24:59

Powiedziałbym, że "najlepszą praktyką" jest przetestowanie czegoś istotnego z obiektem, który jest inny teraz, gdy przechowuje encję na liście.

Innymi słowy, jakie zachowanie różni się od klasy teraz, gdy ją przechowuje, i przetestuj to zachowanie. Przechowywanie jest szczegółem implementacji.

To powiedziawszy, nie zawsze jest to możliwe.

Możesz użyć reflection jeśli musisz.

 2
Author: Yishai,
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:03:05

Jeśli się nie mylę, to co naprawdę chcesz przetestować to to, że dodaje elementy do listy tylko wtedy, gdy mogą być rzucane do IAuditable. Możesz więc napisać kilka testów o nazwach metod, takich jak:

  • NotIAuditableIsNotSaved
  • IAuditableInstanceIsSaved
  • IAuditableSubclassInstanceIsSaved

... i tak dalej.

Problem polega na tym, że, jak zauważyłeś, biorąc pod uwagę kod w twoim pytaniu, możesz to zrobić tylko przez indrection-tylko przez sprawdzenie prywatnego insertItems IList<object> członka (poprzez odbicie lub dodanie właściwości wyłącznie w celu testowania) lub dodanie listy do klasy:

public class ClassToBeTested
{
    private IList _InsertItems = null;

    public ClassToBeTested(IList insertItems) {
      _InsertItems = insertItems;
    }
}

Następnie, to jest proste do przetestowania:

[Test]
public void OnSave_Adds_Object_To_InsertItems_Array()
{
     Setup();

     List<object> testList = new List<object>();
     myClassToBeTested     = new MyClassToBeTested(testList);

     // ... create audiableObject here, etc.
     myClassToBeTested.OnSave(auditableObject, null);

     // Check auditableObject has been added to testList
}

Injection jest najbardziej przyszłościowym i dyskretnym rozwiązaniem, chyba że masz jakiś powód, aby sądzić, że lista byłaby cenną częścią twojego publicznego interfejsu(w takim przypadku dodanie właściwości może być lepsze - i oczywiście property injection jest również całkowicie legalne). Można nawet zachować konstruktor bez argumentów, który dostarcza domyślną implementację (new List ()).

Jest to rzeczywiście dobra praktyka; może Ci się wydawać, że trochę za bardzo przemyślana, biorąc pod uwagę, że jest to prosta klasa, ale sama testowalność jest tego warta. Następnie, jeśli znajdziesz inne miejsce, w którym chcesz korzystać z klasy, będzie to wisienka na torcie, ponieważ nie będziesz ograniczał się do używania IList (nie, że potrzeba wiele wysiłku, aby zmienić później).

 1
Author: Jeff Sternal,
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
2009-07-07 16:28:30

Jeśli lista jest wewnętrznym szczegółem implementacji (i wydaje się, że jest), to nie powinieneś jej testować.

Dobre pytanie brzmi, jakie zachowanie byłoby oczekiwane, gdyby element został dodany do listy? Może to wymagać innej metody, aby go uruchomić.

    public void TestMyClass()
    {
        MyClass c = new MyClass();
        MyOtherClass other = new MyOtherClass();
        c.Save(other);

        var result = c.Retrieve();
        Assert.IsTrue(result.Contains(other));
    }

W tym przypadku twierdzę, że poprawnym, widocznym na zewnątrz zachowaniem jest to, że po zapisaniu obiektu zostanie on włączony do pobranej kolekcji.

Jeśli wynik będzie taki, że w przyszłości obiekt passed-in powinien mieć wywołanie do niego w pewnych okolicznościach, wtedy możesz mieć coś takiego (proszę wybaczyć pseudo-API):

    public void TestMyClass()
    {
        MyClass c = new MyClass();
        IThing other = GetMock();
        c.Save(other);

        c.DoSomething();
        other.AssertWasCalled(o => o.SomeMethod());
    }

W obu przypadkach testujesz zewnętrznie widoczne zachowanie klasy, a nie wewnętrzną implementację.

 1
Author: kyoryu,
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
2009-07-08 05:49:27

Liczba potrzebnych testów zależy od złożoności kodu - w przybliżeniu ile jest punktów decyzyjnych. Różne algorytmy mogą osiągnąć ten sam wynik przy różnej złożoności ich implementacji. Jak napisać test, który jest niezależny od wdrożenia i nadal mieć pewność, że masz odpowiednie pokrycie swoich punktów decyzyjnych?

Teraz, jeśli projektujesz większe testy, na przykład na poziomie integracji, to nie, nie chciałbyś pisać do implementacja lub testowanie prywatnych metod, ale pytanie było skierowane do małego, jednostkowego zakresu testowego.

 0
Author: Chris Golledge,
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
2015-09-04 18:39:19