The hardware and bandwidth for this mirror is donated by METANET, the Webhosting and Full Service-Cloud Provider.
If you wish to report a bug, or if you are interested in having us mirror your free-software or open-source project, please feel free to contact us at mirror[@]metanet.ch.

Getting Started with muttest

This guide walks you through your first mutation test run, from setup to interpreting results.

Prerequisites

muttest works with R packages with testthat tests out of the box. If you’re not using a package structure, or not using testthat, see ?TestStrategy for configuration options.

Step 1 — Pick a source file to mutate

Start small. Choose one file from R/ that contains meaningful logic — branching, arithmetic, comparisons. Avoid files that are mostly glue code or just call other functions.

Step 2 — Define a test plan

A test plan describes what to mutate and which mutations to apply:

library(muttest)

plan <- muttest_plan(
  source_files = "R/is_adult.R",
  mutators = comparison_operators()
)

comparison_operators() is a preset that generates mutants by swapping each comparison operator for related alternatives. For >= it produces two mutants: >=> and >=<=.

Step 3 — Run the tests

muttest::muttest(plan, "tests/testthat")

Step 4 — Read the output and improve your tests

Each column in the progress table means:

Column Meaning
K Killed — mutants your tests caught
S Survived — mutants your tests missed
E Errors — mutants that caused unexpected errors
T Total mutants for this mutator/file combination
% Mutation score for this row

The mutation score is Killed / Total × 100%. A row means at least one mutant was killed; an x row means all mutants survived.

Here is a complete example showing a weak test, the live output, and the fix:

Example: is_adult — Missing Boundary Value

A test suite that checks only values clearly on one side of a threshold will let boundary-shift comparison mutants survive. The surviving mutant names the exact input your tests have never exercised. Adding a test at that precise boundary kills it.

The function

is_adult <- function(age) {
  age >= 18
}

is_adult returns TRUE when age is 18 or older, using >=. Swapping to > would incorrectly classify an 18-year-old as a minor.

Weak test

test_that("is_adult returns TRUE for adults", {
  expect_true(is_adult(25))
})

test_that("is_adult returns FALSE for minors", {
  expect_false(is_adult(10))
})

The tests check age 25 (clearly adult) and age 10 (clearly minor). Both inputs return the same result whether the operator is >= or >, so the tests cannot tell the operators apart.

Mutation testing output:

ℹ Mutation Testing
  |   K |   S |   E |   T |   % | Mutator  | File 
✔ |   1 |   0 |   0 |   1 | 100 | >= → <=  | is_adult.R 
x |   1 |   1 |   0 |   2 |  50 | >= → >   | is_adult.R 


── Results ─────────────────────────────────────────────────────────────────────
[ KILLED 1 | SURVIVED 1 | ERRORS 0 | TOTAL 2 | SCORE 50.0% ]

>= → > survives. Changing age >= 18 to age > 18 only affects one input — age = 18 exactly. The tests never pass 18, so the function behaves identically for 25 and 10 under both operators.

The fix

test_that("is_adult returns TRUE for adults", {
  expect_true(is_adult(25))
})

test_that("is_adult returns FALSE for minors", {
  expect_false(is_adult(10))
})

test_that("is_adult returns TRUE at the boundary age", {
  expect_true(is_adult(18))
})

Add a test at the boundary value 18. With >= 18, the condition is TRUE and the function returns TRUE. With > 18, it is FALSE and returns FALSE. This difference kills the mutant.

After the fix:

ℹ Mutation Testing
  |   K |   S |   E |   T |   % | Mutator  | File 
✔ |   1 |   0 |   0 |   1 | 100 | >= → <=  | is_adult.R 
✔ |   2 |   0 |   0 |   2 | 100 | >= → >   | is_adult.R 


── Results ─────────────────────────────────────────────────────────────────────
[ KILLED 2 | SURVIVED 0 | ERRORS 0 | TOTAL 2 | SCORE 100.0% ]

When a comparison mutant survives, find the boundary value implied by the operator and add a test that passes exactly that value.

The feedback loop

  1. Write tests
  2. Run muttest
  3. Find survivors
  4. Strengthen tests
  5. Repeat

Start with one file. Aim for a meaningful score improvement each iteration rather than chasing 100% immediately. A score of 80%+ on critical business logic can be a reasonable target to start from.

Next steps

These binaries (installable software) and packages are in development.
They may not be fully stable and should be used with caution. We make no claims about them.