Mutant Refactoring powers

Knowing your tests are actually worth something.

Occasionally, I’ve found that when doing some refactorings (especially when splitting a class for example), it can be all too easy to include redundant code. Whilst most of the time it’s possible to eliminate that by a combination of careful inspection and keeping the tests green, I’ve found that mutation testing tools can greatly automate the process here.

For a concrete example, I’m working on a personal spaced repetition project, that also serves as an excuse to play with CQRS. The idea is that rather like Anki each card (fact in Anki terms) can have multiple fields, and questions and answers are templated.

In this example, I’ve split the view model repository into two by areas of functionality; one for editing cards (where we see N fields per card), and another for cards as they would appear whilst reviewing (were you just have a front and a back to the card).

However, there’s still some cruft here and there and dead code that we’d like to get rid of. Mutation testing makes it easier to find this, as deductively, if you mutate code that is never called, there should be no changes to the result of your program.

Running mutant over the new SRSRB::CardEditorProjection class, we (eventually) see the following in the final report:

!!! Mutant alive:
rspec:SRSRB::CardEditorProjection#card_for:/Users/cez/projects/srs-rb/lib/srsrb/card_editor_projection.rb:60:235ac
!!!
 @@ -1,4 +1,4 @@
  def card_for(id)
 – cards.get[id]
 + cards.get
  end
Took: (4.74s)
!!! Mutant alive:
rspec:SRSRB::CardEditorProjection#card_for:/Users/cez/projects/srs-rb/lib/srsrb/card_editor_projection.rb:60:81c99
!!!
 @@ -1,4 +1,4 @@
  def card_for(id)
 – cards.get[id]
 + cards[id]
  end
Took: (4.75s)
!!! Mutant alive:
rspec:SRSRB::CardEditorProjection#card_for:/Users/cez/projects/srs-rb/lib/srsrb/card_editor_projection.rb:60:f83c4
!!!
 @@ -1,4 +1,4 @@
  def card_for(id)
 – cards.get[id]
 + id
  end
Took: (4.71s)
!!! Mutant alive:
rspec:SRSRB::CardEditorProjection#card_for:/Users/cez/projects/srs-rb/lib/srsrb/card_editor_projection.rb:60:9e7ae
!!!
 @@ -1,4 +1,4 @@
  def card_for(id)
 – cards.get[id]
 + nil
  end
Took: (4.59s)
!!! Mutant alive:
rspec:SRSRB::CardEditorProjection#card_for:/Users/cez/projects/srs-rb/lib/srsrb/card_editor_projection.rb:60:61efb
!!!
 @@ -1,4 +1,4 @@
 -def card_for(id)
 +def card_for(s8e207872a38234087817)
  cards.get[id]
 end
Took: (4.76s)
!!! Mutant alive:
rspec:SRSRB::CardEditorProjection#card_for:/Users/cez/projects/srs-rb/lib/srsrb/card_editor_projection.rb:60:12b0a
!!!
 @@ -1,4 +1,4 @@
 -def card_for(id)
 +def card_for
  cards.get[id]
 end
Took: (4.64s)

So, mutant has found that for all the ways it knows how to mutate the method #card_for (renaming parameters, deleting code, &c) the result is that the tests still pass. However, the smoking gun is renaming input parametersーby renaming the method parameter without renaming their usages, it’s quite clear that this method never actually gets calledーotherwise the tests for that mutation would fail with a NameError. And by inspection, we can see it’s not referenced, so we can easily excise it.

Of course, the original purpose of mutation testing wasn’t just to find dead code; it’ll also show you where you have missing tests, or an edge case for a behavior has been missed. For example, it’s quite informative to run this once with just your unit tests, and then again including your end to end tests, as that will quite clearly highlight any short-cuts you might have (even unwittingly) taken in your unit tests.