F
FANG
your tests pass. do they bite?
examples/shop
all · 2026-10-29T04:28:21+00:00
10 tests · 0.33s suite

61.9% mutation score

Scope: all files · 63 mutants generated · 10 tests collected

61.9%
C · thin
killed
39
mutants your tests noticed
survived
24
behaviour changes left unchecked
mutants
63
of 63 generated
run time
7.8s
0.33s baseline

Showing 24 mutants. Press / to search, Esc to reset.

shop/cart.py:17 Cart.add comparison survived
- if quantity <= 0:+ if quantity < 0:

`<=` -> `<` in `quantity <= 0`

Add a test at the boundary where this comparison flips.

0.36s
shop/cart.py:18 Cart.add constant survived
- raise ValueError("quantity must be positive")+ raise ValueError("")

string 'quantity must be positive' -> ""

Assert against a different value so this constant is observable.

0.36s
shop/cart.py:19 Cart.add constant survived
- self.items.append({"sku": sku, "price": price, "quantity": quantity})+ self.items.append({"": sku, "price": price, "quantity": quantity})

string 'sku' -> ""

Assert against a different value so this constant is observable.

0.38s
shop/cart.py:23 Cart.subtotal constant survived
- sum(float(item["price"]) * int(item["quantity"]) for item in self.items), 2+ sum(float(item["price"]) * int(item["quantity"]) for item in self.items), 3

`2` -> `3`

Assert against a different value so this constant is observable.

0.45s
shop/cart.py:30 Cart.total constant survived
- return round(subtotal - discount + shipping, 2)+ return round(subtotal - discount + shipping, 3)

`2` -> `3`

Assert against a different value so this constant is observable.

0.45s
shop/inventory.py:13 Inventory.add comparison survived
- if count < 0:+ if count <= 0:

`<` -> `<=` in `count < 0`

Add a test at the boundary where this comparison flips.

0.47s
shop/inventory.py:13 Inventory.add constant survived
- if count < 0:+ if count < 1:

`0` -> `1`

Assert against a different value so this constant is observable.

0.48s
shop/inventory.py:14 Inventory.add constant survived
- raise ValueError("cannot add a negative count")+ raise ValueError("")

string 'cannot add a negative count' -> ""

Assert against a different value so this constant is observable.

0.45s
shop/inventory.py:18 Inventory.take constant survived
- available = self.stock.get(sku, 0)+ available = self.stock.get(sku, 1)

`0` -> `1`

Assert against a different value so this constant is observable.

0.48s
shop/inventory.py:19 Inventory.take comparison survived
- if count > available:+ if count >= available:

`>` -> `>=` in `count > available`

Add a test at the boundary where this comparison flips.

0.47s
shop/inventory.py:22 Inventory.take return survived
- return True+ return None

`return True` -> `return None`

Assert the return value; calling the function is not enough.

0.47s
shop/inventory.py:22 Inventory.take constant survived
- return True+ return False

`True` -> `False`

Assert against a different value so this constant is observable.

0.48s
shop/inventory.py:25 Inventory.available constant survived
- return self.stock.get(sku, 0)+ return self.stock.get(sku, 1)

`0` -> `1`

Assert against a different value so this constant is observable.

0.47s
shop/pricing.py:5 module constant survived
- FREE_SHIPPING_THRESHOLD = 100.0+ FREE_SHIPPING_THRESHOLD = 101.0

`100.0` -> `101.0`

Assert against a different value so this constant is observable.

0.49s
shop/pricing.py:6 module constant survived
- EXPRESS_FEE = 9.99+ EXPRESS_FEE = 10.99

`9.99` -> `10.99`

Assert against a different value so this constant is observable.

0.50s
shop/pricing.py:10 module constant survived
- "basic": 0.0,+ "": 0.0,

string 'basic' -> ""

Assert against a different value so this constant is observable.

0.48s
shop/pricing.py:12 module constant survived
- "gold": 0.10,+ "": 0.10,

string 'gold' -> ""

Assert against a different value so this constant is observable.

0.45s
shop/pricing.py:12 module constant survived
- "gold": 0.10,+ "gold": 1.1,

`0.1` -> `1.1`

Assert against a different value so this constant is observable.

0.45s
shop/pricing.py:18 discount_rate constant survived
- return TIER_RATES.get(tier, 0.0)+ return TIER_RATES.get(tier, 1.0)

`0.0` -> `1.0`

Assert against a different value so this constant is observable.

0.44s
shop/pricing.py:23 discount_for comparison survived
- if total >= FREE_SHIPPING_THRESHOLD and tier == "gold":+ if total > FREE_SHIPPING_THRESHOLD and tier == "gold":

`>=` -> `>` in `total >= FREE_SHIPPING_THRESHOLD`

Add a test at the boundary where this comparison flips.

0.38s
shop/pricing.py:24 discount_for constant survived
- return round(total * 0.15, 2)+ return round(total * 0.15, 3)

`2` -> `3`

Assert against a different value so this constant is observable.

0.38s
shop/pricing.py:25 discount_for constant survived
- return round(total * discount_rate(tier), 2)+ return round(total * discount_rate(tier), 3)

`2` -> `3`

Assert against a different value so this constant is observable.

0.38s
shop/pricing.py:31 shipping_fee return survived
- return EXPRESS_FEE+ return None

`return EXPRESS_FEE` -> `return None`

Assert the return value; calling the function is not enough.

0.38s
shop/pricing.py:32 shipping_fee comparison survived
- if total >= FREE_SHIPPING_THRESHOLD:+ if total > FREE_SHIPPING_THRESHOLD:

`>=` -> `>` in `total >= FREE_SHIPPING_THRESHOLD`

Add a test at the boundary where this comparison flips.

0.36s
shop/cart.py:17 Cart.add conditional killed
- if quantity <= 0:+ if not (quantity <= 0):

negate condition `quantity <= 0`

Test the branch this condition guards, not only the happy path.

killed by tests/test_cart.py::TestCart::test_gold_cart_applies_discount 0.39s
shop/cart.py:17 Cart.add constant killed
- if quantity <= 0:+ if quantity <= 1:

`0` -> `1`

Assert against a different value so this constant is observable.

killed by tests/test_cart.py::TestCart::test_gold_cart_applies_discount 0.38s
shop/cart.py:19 Cart.add statement killed
- self.items.append({"sku": sku, "price": price, "quantity": quantity})+ pass

remove `self.items.append({"sku": sku, "price": price, "quantity": qu...`

Assert the side effect of this call (state, output, or log).

killed by tests/test_cart.py::TestCart::test_gold_cart_applies_discount 0.42s
shop/cart.py:19 Cart.add constant killed
- self.items.append({"sku": sku, "price": price, "quantity": quantity})+ self.items.append({"sku": sku, "": price, "quantity": quantity})

string 'price' -> ""

Assert against a different value so this constant is observable.

killed by tests/test_cart.py::TestCart::test_gold_cart_applies_discount 0.42s
shop/cart.py:19 Cart.add constant killed
- self.items.append({"sku": sku, "price": price, "quantity": quantity})+ self.items.append({"sku": sku, "price": price, "": quantity})

string 'quantity' -> ""

Assert against a different value so this constant is observable.

killed by tests/test_cart.py::TestCart::test_gold_cart_applies_discount 0.41s
shop/cart.py:22 Cart.subtotal return killed
- return round(
            sum(float(item["price"]) * int(item["quantity"]) for item in self.items), 2
        )+ return None

`return round( sum(float(item["price"]) * int(item["quantity"]...` -> `return None`

Assert the return value; calling the function is not enough.

killed by tests/test_cart.py::TestCart::test_empty_cart 0.50s
shop/cart.py:23 Cart.subtotal constant killed
- sum(float(item["price"]) * int(item["quantity"]) for item in self.items), 2+ sum(float(item[""]) * int(item["quantity"]) for item in self.items), 2

string 'price' -> ""

Assert against a different value so this constant is observable.

killed by tests/test_cart.py::TestCart::test_gold_cart_applies_discount 0.48s
shop/cart.py:23 Cart.subtotal arithmetic killed
- sum(float(item["price"]) * int(item["quantity"]) for item in self.items), 2+ sum(float(item["price"]) / int(item["quantity"]) for item in self.items), 2

`*` -> `/` in `float(item["price"]) * int(item["quantity"])`

Assert the exact numeric result, not just its type.

killed by tests/test_cart.py::TestCart::test_total_adds_shipping 0.50s
shop/cart.py:23 Cart.subtotal constant killed
- sum(float(item["price"]) * int(item["quantity"]) for item in self.items), 2+ sum(float(item["price"]) * int(item[""]) for item in self.items), 2

string 'quantity' -> ""

Assert against a different value so this constant is observable.

killed by tests/test_cart.py::TestCart::test_gold_cart_applies_discount 0.50s
shop/cart.py:30 Cart.total return killed
- return round(subtotal - discount + shipping, 2)+ return None

`return round(subtotal - discount + shipping, 2)` -> `return None`

Assert the return value; calling the function is not enough.

killed by tests/test_cart.py::TestCart::test_empty_cart 0.50s
shop/cart.py:30 Cart.total arithmetic killed
- return round(subtotal - discount + shipping, 2)+ return round(subtotal + discount + shipping, 2)

`-` -> `+` in `subtotal - discount`

Assert the exact numeric result, not just its type.

killed by tests/test_cart.py::TestCart::test_gold_cart_applies_discount 0.52s
shop/cart.py:30 Cart.total arithmetic killed
- return round(subtotal - discount + shipping, 2)+ return round(subtotal - discount - shipping, 2)

`+` -> `-` in `subtotal - discount + shipping`

Assert the exact numeric result, not just its type.

killed by tests/test_cart.py::TestCart::test_empty_cart 0.51s
shop/cart.py:33 Cart.__len__ return killed
- return len(self.items)+ return None

`return len(self.items)` -> `return None`

Assert the return value; calling the function is not enough.

killed by tests/test_cart.py::TestCart::test_len 0.50s
shop/inventory.py:13 Inventory.add conditional killed
- if count < 0:+ if not (count < 0):

negate condition `count < 0`

Test the branch this condition guards, not only the happy path.

killed by tests/test_inventory.py::TestInventory::test_add_and_take 0.50s
shop/inventory.py:15 Inventory.add constant killed
- self.stock[sku] = self.stock.get(sku, 0) + count+ self.stock[sku] = self.stock.get(sku, 1) + count

`0` -> `1`

Assert against a different value so this constant is observable.

killed by tests/test_inventory.py::TestInventory::test_add_and_take 0.50s
shop/inventory.py:15 Inventory.add arithmetic killed
- self.stock[sku] = self.stock.get(sku, 0) + count+ self.stock[sku] = self.stock.get(sku, 0) - count

`+` -> `-` in `self.stock.get(sku, 0) + count`

Assert the exact numeric result, not just its type.

killed by tests/test_inventory.py::TestInventory::test_add_and_take 0.50s
shop/inventory.py:19 Inventory.take conditional killed
- if count > available:+ if not (count > available):

negate condition `count > available`

Test the branch this condition guards, not only the happy path.

killed by tests/test_inventory.py::TestInventory::test_add_and_take 0.50s
shop/inventory.py:20 Inventory.take constant killed
- raise ValueError("not enough stock for %s" % sku)+ raise ValueError("" % sku)

string 'not enough stock for %s' -> ""

Assert against a different value so this constant is observable.

killed by tests/test_inventory.py::TestInventory::test_cannot_take_more_than_available 0.50s
shop/inventory.py:21 Inventory.take arithmetic killed
- self.stock[sku] = available - count+ self.stock[sku] = available + count

`-` -> `+` in `available - count`

Assert the exact numeric result, not just its type.

killed by tests/test_inventory.py::TestInventory::test_add_and_take 0.53s
shop/inventory.py:25 Inventory.available return killed
- return self.stock.get(sku, 0)+ return None

`return self.stock.get(sku, 0)` -> `return None`

Assert the return value; calling the function is not enough.

killed by tests/test_inventory.py::TestInventory::test_add_and_take 0.53s
shop/pricing.py:7 module constant killed
- STANDARD_FEE = 4.99+ STANDARD_FEE = 5.99

`4.99` -> `5.99`

Assert against a different value so this constant is observable.

killed by tests/test_cart.py::TestCart::test_empty_cart 0.51s
shop/pricing.py:10 module constant killed
- "basic": 0.0,+ "basic": 1.0,

`0.0` -> `1.0`

Assert against a different value so this constant is observable.

killed by tests/test_cart.py::TestCart::test_total_adds_shipping 0.51s
shop/pricing.py:11 module constant killed
- "silver": 0.05,+ "": 0.05,

string 'silver' -> ""

Assert against a different value so this constant is observable.

killed by tests/test_pricing.py::TestPricing::test_silver_gets_base_discount 0.55s
shop/pricing.py:11 module constant killed
- "silver": 0.05,+ "silver": 1.05,

`0.05` -> `1.05`

Assert against a different value so this constant is observable.

killed by tests/test_pricing.py::TestPricing::test_silver_gets_base_discount 0.53s
shop/pricing.py:18 discount_rate return killed
- return TIER_RATES.get(tier, 0.0)+ return None

`return TIER_RATES.get(tier, 0.0)` -> `return None`

Assert the return value; calling the function is not enough.

killed by tests/test_cart.py::TestCart::test_empty_cart 0.48s
shop/pricing.py:23 discount_for conditional killed
- if total >= FREE_SHIPPING_THRESHOLD and tier == "gold":+ if not (total >= FREE_SHIPPING_THRESHOLD and tier == "gold"):

negate condition `total >= FREE_SHIPPING_THRESHOLD and tier == "gold"`

Test the branch this condition guards, not only the happy path.

killed by tests/test_cart.py::TestCart::test_gold_cart_applies_discount 0.44s
shop/pricing.py:23 discount_for boolean killed
- if total >= FREE_SHIPPING_THRESHOLD and tier == "gold":+ if total >= FREE_SHIPPING_THRESHOLD or tier == "gold":

`and` -> `or` in `total >= FREE_SHIPPING_THRESHOLD and tier == "gold"`

Cover a case where only one operand is true.

killed by tests/test_pricing.py::TestPricing::test_silver_gets_base_discount 0.42s
shop/pricing.py:23 discount_for comparison killed
- if total >= FREE_SHIPPING_THRESHOLD and tier == "gold":+ if total >= FREE_SHIPPING_THRESHOLD and tier != "gold":

`==` -> `!=` in `tier == "gold"`

Add a test at the boundary where this comparison flips.

killed by tests/test_cart.py::TestCart::test_gold_cart_applies_discount 0.41s
shop/pricing.py:23 discount_for constant killed
- if total >= FREE_SHIPPING_THRESHOLD and tier == "gold":+ if total >= FREE_SHIPPING_THRESHOLD and tier == "":

string 'gold' -> ""

Assert against a different value so this constant is observable.

killed by tests/test_cart.py::TestCart::test_gold_cart_applies_discount 0.44s
shop/pricing.py:24 discount_for return killed
- return round(total * 0.15, 2)+ return None

`return round(total * 0.15, 2)` -> `return None`

Assert the return value; calling the function is not enough.

killed by tests/test_cart.py::TestCart::test_gold_cart_applies_discount 0.44s
shop/pricing.py:24 discount_for arithmetic killed
- return round(total * 0.15, 2)+ return round(total / 0.15, 2)

`*` -> `/` in `total * 0.15`

Assert the exact numeric result, not just its type.

killed by tests/test_cart.py::TestCart::test_gold_cart_applies_discount 0.42s
shop/pricing.py:24 discount_for constant killed
- return round(total * 0.15, 2)+ return round(total * 1.15, 2)

`0.15` -> `1.15`

Assert against a different value so this constant is observable.

killed by tests/test_cart.py::TestCart::test_gold_cart_applies_discount 0.41s
shop/pricing.py:25 discount_for return killed
- return round(total * discount_rate(tier), 2)+ return None

`return round(total * discount_rate(tier), 2)` -> `return None`

Assert the return value; calling the function is not enough.

killed by tests/test_cart.py::TestCart::test_empty_cart 0.41s
shop/pricing.py:25 discount_for arithmetic killed
- return round(total * discount_rate(tier), 2)+ return round(total / discount_rate(tier), 2)

`*` -> `/` in `total * discount_rate(tier)`

Assert the exact numeric result, not just its type.

killed by tests/test_cart.py::TestCart::test_empty_cart 0.39s
shop/pricing.py:30 shipping_fee conditional killed
- if express:+ if not (express):

negate condition `express`

Test the branch this condition guards, not only the happy path.

killed by tests/test_cart.py::TestCart::test_empty_cart 0.41s
shop/pricing.py:32 shipping_fee conditional killed
- if total >= FREE_SHIPPING_THRESHOLD:+ if not (total >= FREE_SHIPPING_THRESHOLD):

negate condition `total >= FREE_SHIPPING_THRESHOLD`

Test the branch this condition guards, not only the happy path.

killed by tests/test_cart.py::TestCart::test_empty_cart 0.44s
shop/pricing.py:33 shipping_fee return killed
- return 0.0+ return None

`return 0.0` -> `return None`

Assert the return value; calling the function is not enough.

killed by tests/test_cart.py::TestCart::test_gold_cart_applies_discount 0.41s
shop/pricing.py:33 shipping_fee constant killed
- return 0.0+ return 1.0

`0.0` -> `1.0`

Assert against a different value so this constant is observable.

killed by tests/test_cart.py::TestCart::test_gold_cart_applies_discount 0.41s
shop/pricing.py:34 shipping_fee return killed
- return STANDARD_FEE+ return None

`return STANDARD_FEE` -> `return None`

Assert the return value; calling the function is not enough.

killed by tests/test_cart.py::TestCart::test_empty_cart 0.41s

Files

filekilledsurvivedscore
shop/cart.py13572%
shop/inventory.py7847%
shop/pricing.py191163%
How to read this report

Mutation score is the share of generated mutants your suite detected. A mutant is a small, syntactically valid change to your code: a flipped comparison, a changed constant, a removed return value. If the suite still passes, the mutant survived, which means no test observed that behaviour.

Survivors are not automatically bugs. Some mutants are equivalent (the change cannot alter observable behaviour, such as changing the rounding precision of a value that is already exact). Treat survivors as questions, not verdicts. The hint on each card suggests the kind of assertion that would settle it.

Fang only mutates the lines in scope, runs the suite with fail-fast, and treats a timeout or a crash caused by the mutant as detection. A green baseline is required; a red suite cannot distinguish a surviving mutant from a pre-existing failure.