niedziela, 8 stycznia 2012

Testowanie mutacyjne z PIT Mutation Testing

Przeglądając dzisiaj (a właściwie to wczoraj) blogosferę natrafiłem na ciekawy post Tomka Kaczanowskiego First Glance At PIT Mutation Testing Tool na temat testowania mutacyjnego, za pomocą nowej biblioteki PIT Mutation Testing. Co to jest testowanie mutacyjne to można poczytać na Wikipedii: Testowanie mutacyjne (co ciekawe w tej chwili dostępny jest tylko artykuł w wersji angielskiej i polskiej).

PIT modyfikuje w locie nasz kod produkcyjny, puszcza testy i sprawdza, czy mutacje kodu są wykrywane przez nasze testy. Sprawdzamy dzięki temu jakość naszego kodu testowego, a dokładniej to, jak ściśle zdefiniowaliśmy zachowanie naszego kodu produkcyjnego za pomocą testów. Jest to trochę więcej niż pokrycie kodu testami wyrażone w procentach, gdyż raport ten podpowiada nam co jeszcze należało by przetestować.

Tomek stworzył przykładowy projekt i umieścił na github’ie. Ściągnąłem go, odpaliłem i przeglądnąłem raporty wygenerowane przez PIT’a. Jako że jest to prosty projekt na potrzeby testu tej biblioteki, postanowiłem zastosować go do swojego projektu MFCCChart. Co prawda nowej funkcjonalności do tego projektu już nie dodaję, ale się nim zabawiam testując jakieś ciekawe rozwiązania.

Patrząc na to co może PIT na stronie Mutation testing systems for Java compared w końcowej sekcji: Summary Of Mutation Testing Systems, to narzędzie to nie ma bezpośrednio wsparcia dla Ant’a, ale mamy interfejs linii komend. Przygotowałem więc komendę, dostosowaną do moich potrzeb:

java -cp out\test\MFCCChart;out\production\MFCCChart;testlib\junit-4.8.jar;testlib\mockito-all-1.8.5.jar;testlib\pitest-0.24.jar;lib\forms_rt.jar;lib\jcommon-1.0.16.jar;lib\jfreechart-1.0.13.jar org.pitest.mutationtest.MutationCoverageReport --outputFormats XML,HTML --reportDir reportspitests --targetClasses org.bitbucket.mstachniuk.mfccchart.* --targetTests org.bitbucket.mstachniuk.mfccchart.* --sourceDirs src --verbose --excludedMethods hasCode,equals --excludedClasses org.bitbucket.mstachniuk.mfccchart.view.*,org.bitbucket.mstachniuk.mfccchart.presenter.*

Najpierw mamy definicję naszego classpath’a. W katalogu out\test\MFCCChart mój ant’owy skrypt budujący wrzuca skompilowane testowe klasy, a do: out\production\MFCCChart klasy produkcyjne aplikacji. Następnie dołączyłem wszystkie wymagane do uruchomienia aplikacji jak i testów biblioteki. Klasa org.pitest.mutationtest.MutationCoverageReport jest klasą startową projektu PIT, a dalej mamy już argumenty dla tejże aplikacji. Ja zapragnąłem raportu w dwóch formatach (XML i HTML) w katalogu reportspitests. Następnie zapodałem klasy, które mają być mutowane, testowe klasy, kody źródłowe (aby można było zobaczyć gdzie wprowadzano mutacje). Jako że jest to biblioteka, której jeszcze nie znam to warto spojrzeć na szczegóły działania (opcja --verbose)  i wyłączyłem z testowania metody hasCode() i equals(), a także klasy z pakietów, które są odpowiedzialne za GUI i spinanie całości (do kupy).

Początkowo miałem problem z opcjami --outputFormats i --excludedClasses, gdyż nie chciały mi działać. Zgłosiłem nawet błąd na stronie projektu: Issue 23, ale szybko się okazało, że podczas kopiowania flag ze strony z Command Line Quick Start Notepad++ zamias zwykłego myślnika wstawił znak wyglądający niemal tak samo, ale o innym kodzie (najprawdopodobniej z po za zestawu ASCII).

Jak już się przekonałem, że generacja raportów działa, postanowiłem dorzucić tą analizę do skryptu budującego aplikację. Początkowo próbowałem to wykonać za pomocą taska exec ale nie chciało hulać. Po za tym task ten jest zależny od systemu operacyjnego i zarzuciłem go na rzecz taska java. I teraz poszło lepiej:

<target name="pitests">
    <java jvmargs="-Xmx600m" fork="true"
            classpath="${mfccchart.testoutput.dir};${mfccchart.output.dir};${basedir}/lib/jcommon-1.0.16.jar;${basedir}/lib/jfreechart-1.0.13.jar;${basedir}/lib/forms_rt.jar;${basedir}/testlib/junit-4.8.jar;${basedir}/testlib/mockito-all-1.8.5.jar;${basedir}/testlib/pitest-0.24.jar"
            classname="org.pitest.mutationtest.MutationCoverageReport"
            args="--reportDir reportspitests --targetClasses org.bitbucket.mstachniuk.mfccchart.* --targetTests org.bitbucket.mstachniuk.mfccchart.* --sourceDirs src --verbose --excludedMethods hasCode,equals --excludedClasses org.bitbucket.mstachniuk.mfccchart.view.*,org.bitbucket.mstachniuk.mfccchart.presenter.* --outputFormats XML,HTML">
    </java>
</target>


Bez fork'a nie chciało mi działać (zresztą pewnie jak większość tego typu rozszerzeń wywoływanych z Ant'a) Co mogłem to pozamieniałem na ścieżki zdefiniowane w pozostałej części skryptu. Jeszcze podaję sporą ilość ścieżek do konkretnych bibliotek co mi się nie podoba. Podejrzewam, że jest lepszy sposób na to ale mistrzem Ant’a nie jestem. Jak by ktoś wiedział, to proszę o komentarz.

Teraz czas na analizę raportu: „Detected 84 of 93 mutations”. Czyli na 9 różnych mutacji podatny jest mój kod. Patrząc na pokrycie klasowy, to chyba bardzo dobrze to wypadło:

Mutated classesLine coverageMutation coverage
OneMfccFileStatistic100%85%
MfccFileReader 100%97%
FrameInformation 100% 100% 
ChartSettings 100% 100% 
MfccFrame 100% 100% 
StatisticTableModel 100% 80% 

Ja jestem zadowolony z wyniku. Jak ktoś chce to niech sobie przejrzy mój przykładowy raport: MFCCChart_pit_reports_201201080158.zip

Teraz pozostaje tylko rozkminienie czego ode mnie chce ten raport (czyli co jeszcze mogę poprawić w swoich testach), jak i przyjrzenie się konkurencyjnym rozwiązaniom, których zestawienie można obejrzeć na wspominanej już stronie: Mutation testing systems for Java compared.

4 komentarze:

  1. Przyglądałem się temu narzędziu jakiś czas temu. Pamiętam, że nieco mnie rozczarowało nie wykrywając dość poważnych luk w testach. Całe szczęście kod PITa jest napisany całkiem nieźle i można bez problemu implementować i wpinać własne mutacje.

    Co do Twojego raportu, jest dość jasny. Np. w kilku miejscach w StatisticTableModel program usunął wywołania calculateMin/Max/Ave a mimo to testy przeszły.

    BTW sugerowałbym sufiks Avg zamiast Ave - bardziej rozpoznawalny z powodu SQLa.

    OdpowiedzUsuń
    Odpowiedzi
    1. Hi Thomas,

      Apologies for my complete lack of Polish. Can you provide some examples of the sort of gaps that PIT did not detect?

      Usuń
  2. @Tomasz
    Dzięki za wskazówkę Ave -> Avg - poprawię przy najbliższej okazji.

    No i muszę się przyjrzeć dokładniej swoim błędom. Brakuje mi jeszcze plugina do Jenkinsa, aby ten malował ładny wykresik ilości testów, które można zmutować. Projekt jest młody, więc plugin pewnie jeszcze powstanie.

    A testowałeś jeszcze jakieś inne narzędzia tego typu?

    OdpowiedzUsuń
  3. Co do innych narzędzi to obawiam się że czeka Cię rozczarowanie - z tego co jakiś czas temu sprawdzałem, to jedynie PIT jakoś tam sobie działa. Reszta to w dużej mierze jakieś archaiczne i nie nadające się do produkcyjnego wykorzystania próby typu "mam ciekawy pomysł na magisterke" :)

    OdpowiedzUsuń