Witaj na Zine.net online Zaloguj się | Rejestracja | Pomoc

Simon says...

Szymon Pobiega o architekturze i inżynierii oprogramowania
Mityczne 100% pokrycia
W środowisku deweloperskim wciąż żywy jest mit 100% pokrycia kodu testami jednostkowymi. Co gorsza, mit ten ma się równie dobrze (a może nawet lepiej?) wśród decydentów (kierowników, dyrektorów itp.).

Celem poniższej notki jest pokazanie, jak bardzo naiwne jest podejście "100% pokrycia".

Popatrzmy na następujący trywialny kod:

    1 public class SomeBusinessClass

    2 {

    3    private int m_valueToCompare;

    4    public int ValueToCompare

    5    {

    6       get { return m_valueToCompare; }

    7       set { m_valueToCompare = value; }

    8    }

    9 

   10    public bool IsGreaterThan(int value)

   11    {

   12       if (value >= ValueToCompare)

   13       {

   14          //Skomplikowana logika

   15          return true;

   16       }

   17       else

   18       {

   19          //Skomplikowana logika

   20          return false;

   21       }

   22    }

   23 }


Nie zwracajcie, proszę, uwagi na postać tego if-a. Wiem, że można to napisać prościej, ale tak jest bardziej demonstracyjnie. Kod zawiera oczywisty błąd - ktoś pomylił operatory w porównaniu. Oto jak inny ktoś, używając szablonu testowego VisualStudio, mógłby napisać testy:

    1 [TestClass()]

    2 public class DumbSomeBusinessClassTest

    3 {

    4    [TestMethod()]

    5    public void ValueToCompareTest()

    6    {

    7       const int expected = 5;

    8       SomeBusinessClass target = new SomeBusinessClass();        

    9 

   10       target.ValueToCompare = expected;

   11       int actual = target.ValueToCompare;

   12 

   13       Assert.AreEqual(expected, actual);        

   14    }

   15 

   16    [TestMethod()]

   17    public void IsGreaterThanTest()

   18    {

   19       const bool expected1 = false;

   20       const int value1 = 2;

   21 

   22       const bool expected2 = true;

   23       const int value2 = 6;         

   24 

   25       SomeBusinessClass target = new SomeBusinessClass();

   26       target.ValueToCompare = 4;        

   27 

   28       bool actual1 = target.IsGreaterThan(value1);

   29       bool actual2 = target.IsGreaterThan(value2);

   30 

   31       Assert.AreEqual(expected1, actual1);

   32       Assert.AreEqual(expected2, actual2);     

   33    }

   34 }


A następnie uruchomić:



i cieszyć się wspaniałymi statystykami (100% pokrycia):



Niestety wszystko to na nic, bo błąd i tak został niewykryty. Zamiast ślepo dążyć do ideału stu procent, lepiej pomyśleć, co tak naprawdę jest ważne w naszej klasie. Rezultatem takiej sesji myślenia jest klasa testowa napisana zgodnie z reklamowanym tutaj schematem Method_State_Result:

    1 [TestClass()]

    2 public class SmartSomeBusinessClassTest

    3 {     

    4    [TestMethod]

    5    public void IsGreaterThan_ValueBelowThreshold_FalseReturned()

    6    {                 

    7       SomeBusinessClass target = new SomeBusinessClass();

    8       target.ValueToCompare = 4;

    9 

   10       bool actual = target.IsGreaterThan(2);        

   11 

   12       Assert.AreEqual(false, actual);        

   13    }

   14    [TestMethod]

   15    public void IsGreaterThan_ValueEqualsThreshold_FalseReturned()

   16    {

   17       SomeBusinessClass target = new SomeBusinessClass();

   18       target.ValueToCompare = 4;

   19 

   20       bool actual = target.IsGreaterThan(4);

   21 

   22       Assert.AreEqual(false, actual);

   23    }

   24    [TestMethod]

   25    public void IsGreaterThan_ValueAboveThreshold_FalseReturned()

   26    {

   27       SomeBusinessClass target = new SomeBusinessClass();

   28       target.ValueToCompare = 4;

   29 

   30       bool actual = target.IsGreaterThan(6);

   31 

   32       Assert.AreEqual(true, actual);

   33    }

   34 }


Co ciekawe, klasa ta ma dokładnie tyle samo linii, co jej "głupia" wersja. Tym razem uzyskujemy "jedynie" około (w zależności od metryki) 90 procent pokrycia. Ale za to:



Jest jeszcze jeden bardzo ważny negatywny aspekt głupiego dążenia do 100% pokrycia. Otóż daje ono autorowi fałszywe poczucie pewności, iż przetestowana "idealnie" klasa jest bezbłędna. Przekonanie, że pełne pokrycie testami gwarantuje bezbłędność jest tak silne wśród programistów, że wręcz powoduje wyłączenie zdrowego rozsądku: Przecież mam 100% pokrycia! To na pewno nie mój błąd!

Nie dajmy się zwieść mitom. Kierujmy się zdrowym rozsądkiem. Mądre 85% jest lepsze, niż naiwne 100%.

Opublikowane 18 września 2009 14:14 przez simon

Filed under:

Komentarze:

# Simon says... : Mityczne 100% pokrycia @ 18 września 2009 19:13

Dziękujemy za publikację - Trackback z dotnetomaniak.pl

dotnetomaniak.pl

# re: Mityczne 100% pokrycia @ 18 września 2009 22:59

No to teraz sobie wyobraź ze ktoś dał ciała i tak napisał:

   4    public int ValueToCompare

   5    {

   6       get { return m_valueToCompare; }

   7       set { m_valueToCompare = value+1; }

   8    }

Drugi przykład po uzupełnieniu  też będzie pokrywł 100% kodu, natomiast pierwszy przykład pomimo ze pokrywa 100% kodu to nie pokrywa 100% możliwych przypadków.

Właściwie to idąc zgodnie z przedstawionym przykładem powinniśmy testować tylko te fragmenty kodu w których sa błędy:)

no bo jeśli mogliśmy usunąć metodę ValueToCompareTest, to równe dobrze możemy usunąć metody IsGreaterThan_ValueBelowThreshold_FalseReturned oraz IsGreaterThan_ValueAboveThreshold_FalseReturned a test nadal bedzie się wywalał.

Myślę że przedstawiony przykład jest dobry na pokazanie że nawet 100% pokrycie nie gwarantuje 100% pewności, natomiast (jak sądzę) 100% to jest to do czego dążyć należy.

Pozdrawiam wszystkich.

florek

# re: Mityczne 100% pokrycia @ 22 września 2009 16:57

@Florek

Kod, który przedstawiłeś rzeczywiście łamie mój przykład, ale z drugiej strony - jest to kod, który nie trzyma się _standardowych konwencji_. Kod property nie powinien mieć zaszytej logiki biznesowej. Jeśli ma - owszem, warto go przetestować. Ale jeśli nie - jest to strata czasu. Warto pamiętać, że każda napisana linijka kodu (także kodu testów) wprowadza dodatkowy koszt kiedy przyjdzie nam system utrzymywać. Warto wiec pozbywać się każdego kawałka kodu, który nic nam nie daje.

simon

# re: Mityczne 100% pokrycia @ 24 września 2009 11:22

Oczywiście masz całkowitą rację, ale ... wszystko to przekreśliłeś pierwszym przykładem gdzie property jest testowane.

Porównałeś dwa przykłady które nie są równoważne, co w mojej opinii doprowadziło do wyciągnięcia niewłaściwych wniosków i to starałem się wykazać.  

Jeszcze raz pozdrawiam

florek

florek

# re: Mityczne 100% pokrycia @ 24 września 2009 11:46

@Florek

W pierwszym przykładzie chciałem pokazac bezsensowność testów generowanych przez VS: generują testy do własności, które _by convention_ nie powinny być błędogenne. Co prawda jest to tylko konwencja i każdy może napisać dowolnie skomplikowane property, jednak dobrą praktyką jest pisanie ich tak, aby nie trzeba było ich testować.

Chciałbym podkreślić, że jeśli coś nie wymaga testowania, bo widać na pierwszy rzut okaz, że nie wymaga, to nie powinno być testowane. Pewnie zostane przez wszystkich ukamienowany za to stwierdzenie;)

Popatrzmy na to od drugiej strony: jeśli robię TDD, piszę testy (specyfikację) do zachowania moich obiektów. Zwykle nie specyfikuje że property powinno zwracać to, co mu się ustawi, bo to oczywiste. Analogicznie, testy pisane po implementacji nie powinny sprawdzać tego co oczywiste.

Oczywiście to wszystko można stosować wtedy, kiedy ma się zaufanie do siebie lub swoich programistów, że "oczywistość" jest dobrze rozumiana;)

simon

# zkawalec@FP » Blog Archive » mityczne 100% pokrycia @ 23 października 2009 09:45

PingBack od http://future-processing.com/fpdev/blogs/zkawalec/2009/10/23/mityczne-100-pokrycia/

zkawalec@FP » Blog Archive » mityczne 100% pokrycia

# Re: NHibernateStarter... @ 1 grudnia 2009 09:05

Czyli nawiązanie do świetnego posta Procent-a na temat jego sposobu budowy aplikacji. Poniżej zamieszczam

Simon says...

# Re: NHibernateStarter... @ 1 grudnia 2009 09:13

Czyli nawiązanie do świetnego posta Procent-a na temat jego sposobu budowy aplikacji. Poniżej zamieszczam

Simon says...

Komentarze anonimowe wyłączone