Kuba Werłos
function shippingCost(Order $order): float
{
$cost = 15.00;
if ($order->weightKg > 30.0) {
$cost += 9.00;
}
if ($order->isExpress) {
$cost *= 2;
}
return $cost;
}function testShippingCost(): void
{
$order = new Order(weightKg: 40.0, isExpress: true);
self::assertGreaterThan(0, shippingCost($order));
}function shippingCost(Order $order): float
{
$cost = 15.00;
if ($order->weightKg >= 30.0) {
$cost += 9.00;
}
if ($order->isExpress) {
$cost *= 2;
}
return $cost;
}function testShippingCost(): void
{
$order = new Order(weightKg: 40.0, isExpress: true);
self::assertGreaterThan(0, shippingCost($order));
}function shippingCost(Order $order): float
{
$cost = 15.00;
if ($order->weightKg > 30.0) {
$cost -= 9.00;
}
if ($order->isExpress) {
$cost *= 2;
}
return $cost;
}function testShippingCost(): void
{
$order = new Order(weightKg: 40.0, isExpress: true);
self::assertGreaterThan(0, shippingCost($order));
}function shippingCost(Order $order): float
{
$cost = 15.00;
if ($order->weightKg > 30.0) {
$cost += 9.00;
}
if ($order->isExpress) {
$cost /= 2;
}
return $cost;
}function testShippingCost(): void
{
$order = new Order(weightKg: 40.0, isExpress: true);
self::assertGreaterThan(0, shippingCost($order));
}function lateFee(float $amount, int $daysLate): float
{
$fee = 0.0;
if ($daysLate > 14) {
$fee = $amount * 0.05;
}
if ($fee > 100.0) {
$fee = 100.0;
}
return $fee;
}function testLateFee(): void
{
$fee = lateFee(5_000.0, daysLate: 30);
self::assertSame(100.0, $fee);
}function lateFee(float $amount, int $daysLate): float
{
$fee = 1.0;
if ($daysLate > 14) {
$fee = $amount * 0.05;
}
if ($fee > 100.0) {
$fee = 100.0;
}
return $fee;
}function testLateFee(): void
{
$fee = lateFee(5_000.0, daysLate: 30);
self::assertSame(100.0, $fee);
}function lateFee(float $amount, int $daysLate): float
{
$fee = 0.0;
if ($daysLate >= 14) {
$fee = $amount * 0.05;
}
if ($fee > 100.0) {
$fee = 100.0;
}
return $fee;
}function testLateFee(): void
{
$fee = lateFee(5_000.0, daysLate: 30);
self::assertSame(100.0, $fee);
}function lateFee(float $amount, int $daysLate): float
{
$fee = 0.0;
if ($daysLate > 14) {
$fee = $amount / 0.05;
}
if ($fee > 100.0) {
$fee = 100.0;
}
return $fee;
}function testLateFee(): void
{
$fee = lateFee(5_000.0, daysLate: 30);
self::assertSame(100.0, $fee);
}| co psuje? | czego pilnuje? | |
|---|---|---|
| chaos engineering | infrastrukturę | odporności systemu |
| fuzzing | dane wejściowe | odporności kodu |
| ? | kod | jakości testów |
$daysLate > 14Expr_BinaryOp_Greater(
left: Expr_Variable(
name: daysLate
)
right: Scalar_Int(
value: 14
)
)$daysLate >= 14Expr_BinaryOp_GreaterOrEqual(
left: Expr_Variable(
name: daysLate
)
right: Scalar_Int(
value: 14
)
)$a + $b⟶$a - $bdla każdego węzła AST
dla każdego mutatora, który go obsługuje
dla każdej możliwej mutacji
mutuj
uruchom testy0.35.4
composer require --dev infection/infectionvendor/bin/infectioninfection.json5{
"staticAnalysisTool": "phpstan",
"source": {
"directories": ["src"]
},
"mutators": {
"global-ignoreSourceCodeByRegex": [
// don't touch: crucial for tool to work properly
".*\\\\PHP_VERSION_ID.*",
],
"@default": true,
// …
"LogicalNot": {
"ignore": [
"PhpCsFixer\\Runner\\Runner::fixFile"
]
}
}
}array_filter($x)⟶$x[1, 2, 3]⟶[2, 3]$em->flush();⟶∅return $foo;⟶∅$a + $b⟶$a - $btrue⟶falsefloor($x)⟶ceil($x)$a > $b⟶$a >= $b$a !== $b⟶$a != $b| kategoria | ile? |
|---|---|
| zmniejszenie znaczenia | 104 |
| zamiana znaczenia | 59 |
| zwiększenie znaczenia | 3 |
razem 166 mutatorów
w @default 158
vendor/bin/infection make:mutator MethodBodyRemovalZmiana nie została wykryta przez testy
$this->logger->info('Zapisano');⟶∅function capPercent(int $percent): int
{
if ($percent > 100) {
$percent = 100;
}
return $percent;
}function capPercent(int $percent): int
{
if ($percent >= 100) {
$percent = 100;
}
return $percent;
}| stan | co się stało? |
|---|---|
| złapany | test padł |
| uciekł | testy przeszły |
| niepokryty | żaden test nie dotknął tej linii |
| timeout | mutacja zapętliła program |
| niepoprawny | kod się nie uruchamia albo się wywala |
Mutation Score Indicator
złapane + timeout + niepoprawne
MSI = ──────────────────────────────────────────────────────
złapane + timeout + niepoprawne + uciekły + niepokryteCovered MSI
ten sam licznik,
mianownik bez niepokrytych
.: killed by tests, A: killed by SA, M: escaped, U: uncovered
E: fatal error, X: syntax error, T: timed out, S: skipped, I: ignored
MMMMMMMM.MM.MMUUUUU.. (21 / 21)
Escaped mutants:
================
1) src/Calculator.php:9 [M] GreaterThan
@@ @@
{
$cost = 15.00;
- if ($order->weightKg > 30.0) {
+ if ($order->weightKg >= 30.0) {
$cost += 9.00;
}
21 mutations were generated:
4 mutants were killed by Test Framework
5 mutants were not covered by tests
12 covered mutants were not detected
Metrics:
Mutation Score Indicator (MSI): 19%
Mutation Code Coverage: 76%
Covered Code MSI: 25%| biblioteka | pokrycie | MSI |
|---|---|---|
| lcobucci/jwt | 100% | 98,1% |
| nesbot/carbon | 99,9% | 79,0% |
| beberlei/assert | 99,8% | 75,1% |
| webmozart/assert | 96,3% | 70,1% |
| symfony/yaml | 93,7% | 87,0% |
| vlucas/phpdotenv | 93,2% | 83,9% |
| guzzlehttp/guzzle | 90,3% | 91,0% |
| firebase/php-jwt | 86,0% | 76,0% |
| twig/twig | 85,2% | 78,4% |
| monolog/monolog | 70,9% | 52,0% |
$i++⟶$i----threads=max--git-diff-lines--min-msi=80--logger-html=infection.htmlDwie zmiany lub więcej naraz zamiast jednej
Jeden test: average(5, 3) ma dać 4
($a + $b) / 2 ⟶ 4 oryginał
($a - $b) / 2 ⟶ 1 złapany
($a + $b) * 2 ⟶ 16 złapany
($a - $b) * 2 ⟶ 4 uciekłTo był mutant pochłaniający —
dwa błędy zamaskowały się nawzajem
Jeden trudny mutant zamiast kilku łatwych
Dziękuję za uwagę