Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Can You Trust Your Tests (Agile Tour 2015 Kaunas)

Can You Trust Your Tests (Agile Tour 2015 Kaunas)

Nobody argues these days that unit tests are useful and provide valuable feedback about your code. But who watches the watchmen? Let's talk about test code quality, code coverage and introduce mutation based testing techniques.

Vaidas Pilkauskas

October 01, 2015
Tweet

More Decks by Vaidas Pilkauskas

Other Decks in Programming

Transcript

  1. Agenda 1. Test quality & code coverage 2. Mutation testing

    in theory 3. Mutation testing in practice
  2. “Program testing can be used to show the presence of

    bugs, but never to show their absence!” - Edsger W. Dijkstra
  3. Lines string foo(boolean arg) { return arg ? "a" :

    "b" } assertThat(a.foo(true), is("a"))
  4. Branches string foo(boolean arg) { return arg ? "a" :

    "b" } assertThat(a.foo(true), is("a")) assertThat(a.foo(false), is("b"))
  5. Can you trust 100% coverage? Code coverage can only show

    what is not tested. For interpreted languages 100% code coverage is kind of like full compilation.
  6. What exactly is a mutation? def isFoo(a) { return a

    == foo } def isFoo(a) { return a != foo } def isFoo(a) { return true } def isFoo(a) { return null } > > >
  7. Terminology Applying a mutation to some code creates a mutant.

    If test passes - mutant has survived. If test fails - mutant is killed.
  8. // test max([0]) == 0 ✔ max([1]) == 1 ✘

    // implementation max(a) { return 0 }
  9. // test max([0]) == 0 ✔ max([1]) == 1 ✔

    // implementation max(a) { return a.first }
  10. // test max([0]) == 0 ✔ max([1]) == 1 ✔

    max([0, 2]) == 2 ✘ // implementation max(a) { return a.first }
  11. // test max([0]) == 0 ✔ max([1]) == 1 ✔

    max([0, 2]) == 2 ✔ // implementation max(a) { m = a.first for (e in a) if (e > m) m = e return m }
  12. Mutation // test max([0]) == 0 ✔ max([1]) == 1

    ✔ max([0, 2]) == 2 ✔ // implementation max(a) { m = a.first for (e in a) if (e > m) m = e return m }
  13. Mutation // test max([0]) == 0 ✔ max([1]) == 1

    ✔ max([0, 2]) == 2 ✔ // implementation max(a) { m = a.first for (e in a) if (true) m = e return m }
  14. // test max([0]) == 0 ✔ max([1]) == 1 ✔

    max([0, 2]) == 2 ✔ // implementation max(a) { return a.last }
  15. // test max([0]) == 0 ✔ max([1]) == 1 ✔

    max([0, 2]) == 2 ✔ max([2, 1]) == 2 ✘ // implementation max(a) { return a.last }
  16. // implementation max(a) { m = a.first for (e in

    a) if (e > m) m = e return m } // test max([0]) == 0 ✔ max([1]) == 1 ✔ max([0, 2]) == 2 ✔ max([2, 1]) == 2 ✔
  17. It’s like hiring a white-hat hacker to try to break

    into your server and making sure you detect it.
  18. What if mutant survives • Simplify your code • Add

    additional tests • TDD - minimal amount of code to pass the test
  19. Equivalent mutations // Original int i = 0; while (i

    != 10) { doSomething(); i += 1; } // Mutant int i = 0; while (i < 10) { doSomething(); i += 1; }
  20. Let’s say we have codebase with: • 300 classes •

    around 10 tests per class • 1 test runs around 1ms • total test suite runtime is about 3s Is it really slow? Let’s do 10 mutations per class • We get 3000 (300 * 10) mutations • runtime with all mutations is 150 minutes (3s * 3000)
  21. Speeding it up Run only tests that cover the mutation

    • 300 classes • 10 tests per class • 10 mutations per class • 1ms test runtime • total mutation runtime 10 * 10 * 1 * 300 = 30s
  22. • Continuous integration • TDD with mutation testing only on

    new changes • Add mutation testing to your legacy project, but do not fail a build - produce warning report Usage scenarios
  23. Tools • Ruby - Mutant • Java - PIT •

    And many tools for other languages
  24. Summary • Code coverage highlights code that is definitely not

    tested • Mutation testing highlights code that definitely is tested • Given non equivalent mutations, good test suite should work the same as a hash function
  25. Vaidas Pilkauskas @liucijus • Vilnius JUG co-founder • Vilnius Scala

    leader • Coderetreat facilitator • Mountain bicycle rider • Snowboarder About us Tadas Ščerbinskas @tadassce • VilniusRB co-organizer • RubyConfLT co- organizer • RailsGirls Vilnius & Berlin coach • Various board sports’ enthusiast
  26. Credits A lot of presentation content is based on work

    by these guys • Markus Schirp - author of Mutant • Henry Coles - author of PIT • Filip Van Laenen - working on a book
  27. Q&A