Confidence course

Mutation Testing

Five runnable lessons covering vary mutate, scores, survivors, boundary mutations, and the inner-loop workflow.

For each lesson, copy the code into the filename shown in the terminal, run the command next to the lesson, then paste the Mutation score line into the answer box and validate it. Your score is saved in this browser, so you can leave and come back later on the same device.

Section 1

First mutations

Run vary mutate, see survivors, then pin the behavior with stronger tests.

free_shipping.vary
pure def has_free_shipping(order_total: Int) -> Bool { return order_total >= 50 } test "larger order qualifies" { observe has_free_shipping(75) == True }

Lesson 1 / free_shipping.vary

Run your first mutation pass

See that one happy-path test leaves most mutants alive.

vary mutate rewrites the implementation in small ways (flipping a comparison, returning a constant, dropping a statement) and checks whether the tests still pass. Each survivor is a place where the tests would not have noticed a bug. With one happy-path test the score is intentionally low.

Run vary mutate free_shipping.vary --tests free_shipping.vary --quick
Expected output
Accepted pattern (?m)Mutation score: \d{1,2}%
free_shipping.vary
pure def has_free_shipping(order_total: Int) -> Bool { return order_total >= 50 } test "larger order qualifies" { observe has_free_shipping(75) == True } test "smaller order does not qualify" { observe has_free_shipping(20) == False } test "exactly 50 qualifies" { observe has_free_shipping(50) == True } test "49 does not qualify" { observe has_free_shipping(49) == False }

Lesson 2 / free_shipping.vary

Pin down the survivors

Add a boundary and an opposite-branch test until the score hits 100%.

Each new test is a constraint on the implementation. Boundary cases and the opposite branch typically expose the most survivors, because mutations like >= becoming > only break at the edge.

Run vary mutate free_shipping.vary --tests free_shipping.vary --quick
Expected output
Accepted pattern (?m)Mutation score: 100%

Section 2

Common survivors

Why boundary tests do most of the work.

is_2xx.vary
pure def is_2xx(status: Int) -> Bool { return status >= 200 and status < 300 } test "200 is 2xx" { observe is_2xx(200) == True } test "404 is not 2xx" { observe is_2xx(404) == False }

Lesson 3 / is_2xx.vary

Boundary mutants survive without edge tests

See how range predicates leak mutants when tests skip the edges.

A predicate like status >= 200 and status < 300 has four boundaries (200, 199, 299, 300). A test that only checks 200 and 404 proves nothing at the upper edge, and vary mutate exposes that gap as surviving mutants.

Run vary mutate is_2xx.vary --tests is_2xx.vary --quick
Expected output
Accepted pattern (?m)Mutation score: \d{1,2}%
is_2xx.vary
pure def is_2xx(status: Int) -> Bool { return status >= 200 and status < 300 } test "200 is the lower edge" { observe is_2xx(200) == True observe is_2xx(199) == False } test "299 is the upper edge" { observe is_2xx(299) == True observe is_2xx(300) == False } test "404 is not 2xx" { observe is_2xx(404) == False }

Lesson 4 / is_2xx.vary

Lock in every edge

Cover all four boundaries of a range predicate.

Asserting both sides of every edge (200 and 199, 299 and 300) closes the boundary family. The same code suddenly scores 100% because there is no way to perturb a comparison without breaking one of the edge tests.

Run vary mutate is_2xx.vary --tests is_2xx.vary --quick
Expected output
Accepted pattern (?m)Mutation score: 100%

Section 3

Practical use

What 100% looks like on real branching logic.

class_of.vary
pure def class_of(status: Int) -> Str { if status >= 500 { return "5xx" } if status >= 400 { return "4xx" } if status >= 200 { return "2xx" } return "other" } test "200 is the 2xx lower edge" { observe class_of(200) == "2xx" observe class_of(199) == "other" } test "400 is the 4xx lower edge" { observe class_of(400) == "4xx" observe class_of(399) == "2xx" } test "500 is the 5xx lower edge" { observe class_of(500) == "5xx" observe class_of(499) == "4xx" } test "deep 5xx still classifies as 5xx" { observe class_of(503) == "5xx" }

Lesson 5 / class_of.vary

Mutation in the inner loop

Hit 100% on a multi-branch classifier with one edge pair per threshold.

Most real logic is a tree of comparisons. Hit 100% by writing one boundary pair per threshold; vary mutate --quick then becomes a cheap inner-loop check that your test suite still constrains the same shape after every edit.

Run vary mutate class_of.vary --tests class_of.vary --quick
Expected output
Accepted pattern (?m)Mutation score: 100%

Course score

0/5 lessons complete

Validate each lesson output to finish this course.

Next course

Install and Start Via Server Prepare an Ubuntu host, confirm Vary and Via are installed, install Via prerequisites, initialize the control plane, and verify the Via systemd services. Continue