Krótkie wprowadzenie do testów mutacyjnych

W ramach zabawy z lottomatem piszemy bardzo dużo testów. Pytanie, czy testy te są dobre. Jednym ze sposobów sprawdzenia, czy nasze testy są dobre, jest podmienienie fragmentów kodu. Jeżeli testy rzeczywiście coś testują, to taka podmiana powinna zostać wykryta. Testy nie powinny być zaliczone.

To podejście nazywamy testami mutacyjnymi od zmian, jakie przeprowadzają one w kodzie. Jednym z narzędzi, jakie możemy wykorzystać jest pitest. Działa on w bardzo intuicyjny i prosty sposób. Po podpięciu do projektu wystarczy uruchomić odpowiedni raport i pitest przeprowadzi wszystkie potrzebne testy.

Jak wygląda lottomat?

Na początek musimy skonfigurować kilka rzeczy w naszym pliku build.gradle. W stosunku do pierwotnej wersji dodałem kilka rzeczy:

Listing 1. Plik build.gradle

/*
 * This build file was auto generated by running the Gradle 'init' task
 * by 'koziolek' at '09.05.16 11:32' with Gradle 2.3
 *
 * This generated file contains a sample Java project to get you started.
 * For more details take a look at the Java Quickstart chapter in the Gradle
 * user guide available at http://gradle.org/docs/2.3/userguide/tutorial_java_projects.html
 */

// Apply the java plugin to add support for Java
apply plugin: 'java'
apply plugin: 'jacoco'

sourceCompatibility = 1.8
targetCompatibility = 1.8

// In this section you declare where to find the dependencies of your project
repositories {
    // Use 'jcenter' for resolving your dependencies.
    // You can declare any Maven/Ivy/file repository here.
    jcenter()
}

// In this section you declare the dependencies for your production and test code
dependencies {
    // The production code uses the SLF4J logging API at compile time
    compile 'org.slf4j:slf4j-api:1.7.7'
    compile 'com.google.guava:guava:19.0'

    // Declare the dependency for your favourite test framework you want to use in your tests.
    // TestNG is also supported by the Gradle Test task. Just change the
    // testCompile dependency to testCompile 'org.testng:testng:6.8.1' and add
    // 'test.useTestNG()' to your build script.
    testCompile 'junit:junit:4.12'
    testCompile 'org.easytesting:fest-assert:1.4'
    testCompile 'org.mockito:mockito-all:1.10.19'
}

jacocoTestReport {
    reports {
        xml.enabled = true
        html.enabled = true
    }
}

test {
    reports {
        junitXml.enabled = true
        html.enabled = true
    }
}

buildscript {
    repositories {
        mavenCentral()
        jcenter()
    }
    dependencies {
        classpath 'info.solidsoft.gradle.pitest:gradle-pitest-plugin:1.1.9'
        classpath 'us.carrclan.david.gradle:gradle-site-plugin:0.2.0'

    }
}
apply plugin: "info.solidsoft.pitest"
apply plugin: 'site'

pitest {
    targetClasses = ['pl.koziolekweb.*']
    threads = 4
    outputFormats = ['XML', 'HTML']
}
sourceSets {
    site {
        resources {
            srcDir 'build/docs'
            srcDir 'build/reports'
        }
    }
}

check.dependsOn(jacocoTestReport, "pitest")

Doszły raporty Jacoco, które będą mówić o pokryciu kodu testami oraz konfiguracja pitest, który wykona za nas testy mutacyjne. Jak uruchomimy całość za pomocą polecenia gradle clean build to w katalogu build/reports znajdziemy nasze raporty. Jak się prezentują? Część dotyczącą pokrycia testami znajdziecie na CodeCov. Wygląda na to, że nie jest źle. Jedyny kod bez pokrycia to ten z pierwszej wersji programu. Jednak jedyny test, jaki przychodzi mi do głowy polega na sprawdzeniu, czy zwrócona przez metodę stat mapa zawiera 49 wpisów:

Listing 2. Test starego Lotto

public class LottoTest {

	@Test
	public void shouldStatReturnMapOf49Entries() throws Exception {
		Lotto sut = new Lotto();

		Map<Integer, Integer> stat = sut.stat(1);

		assertThat(stat).hasSize(49);

	}
}

Możemy spróbować też napisać test, który sprawdzi, czy dla pojedynczego losowania sześć zw wpisów w mapie ma wartość 1. Które? Nie wiadomo, bo po drodze jest element losowy. Można napisać test, który będzie już na tyle skomplikowany, że będzie wymagał testu. Jak sprawdzimy, czy wartości są prawidłowo umieszczane w mapie? No, ale…

No po co testować

Tu z pomocą przychodzi nam pitest. Jego działalność pozwala na ocenę, czy pokrycie kodu jest prawdziwe, czy też jest to tylko wynik radosnego odpalenia aplikacji bez użycia weryfikacji w testach.

Pitest results

Jak spojrzymy do tych pakietów to przekonamy się, że najwięcej problemów jest z klasą Lotto. Nasza nowa wersja ma tylko jeden drobny problem, bo nie sprawdzamy nigdzie co dzieje się gdy usuniemy mieszanie elementów w maszynie. Ten raport będzie dostępny online jak rozkminię jak skonfigurować travisa by aktualizował dane na gh-pages.

6 myśli na temat “Krótkie wprowadzenie do testów mutacyjnych

  1. Pytanie z innej beczki mam- do nowych projektów Gradle czy Maven?

  2. @sweetprograming, no nie do końca gradle, bo jednak ma on znacznie mniej wtyczek niż maven i co za tym idzie trzeba czasami się nakombinować. Z drugiej strony maven nie jest aż tak elastyczny jak gradle.

  3. prawda, nagimnastykować się niekiedy trzeba.
    Ale jaka przyjemność pracy! Ile razy trzeba było pisać własny plugin do mavena żeby coś prostego osiągnąć, bo xml nie wystarczył 🙂

Napisz odpowiedź

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *

To create code blocks or other preformatted text, indent by four spaces:

    This will be displayed in a monospaced font. The first four 
    spaces will be stripped off, but all other whitespace
    will be preserved.
    
    Markdown is turned off in code blocks:
     [This is not a link](http://example.com)

To create not a block, but an inline code span, use backticks:

Here is some inline `code`.

For more help see http://daringfireball.net/projects/markdown/syntax