0, 1, 1, 2, 3, 5, 8, 13, 21, 34 ...
function fibonacci(int $n) {
+ return ($n === 0 || $n === 1) ? $n : fibonacci($n - 1) + fibonacci($n - 2);
}
fibonacci(0); // 0
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd">
<testsuites>
<testsuite name="default">
<directory>tests</directory>
</testsuite>
</testsuites>
<coverage pathCoverage="true">
<include>
<directory suffix=".php">src</directory>
</include>
</coverage>
</phpunit>
Line Coverage, 100%
Tested
Code, 60%
class IdGenerator
{
public function uuid(): string
{
// generating logic ...
return $id;
}
}
class UserCreator
{
public function __construct(
private IdGenerator $idGenerator
) {}
public function create(string $name, int $age): User
{
// some logic ...
return new User(
$this->idGenerator->uuid(),
$age,
$name
);
}
}
class UserCreatorTest extends TestCase
{
public function test(): void
{
$userCreator = new UserCreator(
new IdGenerator()
);
self::assertInstanceOf(
User::class,
$userCreator->create('Ivan', 33)
);
// .. other asserts
}
}
<phpunit>
<!-- ... -->
<listeners>
<listener class="Symfony\Bridge\PhpUnit\CoverageListener" />
</listeners>
</phpunit>
/**
* @covers App\UserCreator
*/
class UserCreatorTest extends TestCase
{
public function test(): void
{
$userCreator = new UserCreator(
new IdGenerator()
);
// ...
}
}
Coverage shows what code is not tested
- $a = $b + $c;
+ $a = $b - $c;
Original | Mutated |
---|---|
> | >= |
=== | !== |
&& | || |
'/^test$/' | '/test$/' and '/^test/' |
return true; | return false; |
foreach ($someVar as …) | foreach ([] as …); |
... | ... |
MSI = (TotalKilledMutants / TotalMutantsCount) * 100;
Test # Mutation # |
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Result |
---|---|---|---|---|---|---|---|---|---|
1 ("+" ➝ "-") | ✅ | ✅ | ✅ | ❌ | Killed | ||||
2 (">" ➝ ">=") | ⏳ | ||||||||
3 ("==" ➝ "!=) | |||||||||
4 (true ➝ false) |
Test # Mutation # |
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Result |
---|---|---|---|---|---|---|---|---|---|
1 ("+" ➝ "-") | ✅ | ✅ | ✅ | ❌ | Killed | ||||
2 (">" ➝ ">=") | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | Escaped |
3 ("==" ➝ "!=) | ⏳ | ||||||||
4 (true ➝ false) |
Test # Mutation # |
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Result |
---|---|---|---|---|---|---|---|---|---|
1 ("+" ➝ "-") | ✅ | ✅ | ✅ | ❌ | Killed | ||||
2 (">" ➝ ">=") | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | Escaped |
3 ("==" ➝ "!=) | ✅ | ❌ | Killed | ||||||
4 (true ➝ false) | ✅ | ✅ | ❌ |
class UserAgeFilter
{
private const AGE_THRESHOLD = 18;
private $ageThreshold;
public function __construct(int $ageThreshold = self::AGE_THRESHOLD)
{
$this->ageThreshold = $ageThreshold;
}
/** @param array<int, User> $collection */
public function __invoke(array $collection)
{
return array_filter(
$collection,
function (User $user) {
return $user->getAge() >= $this->ageThreshold;
}
);
}
}
public function test_it_filters_adults(): void
{
$users = [
User::withAge(20),
];
$filter = new UserAgeFilter();
$filteredUsers = $filter($users);
assertCount(1, $filteredUsers);
}
class UserAgeFilter
{
// ...
/** @param array<int, User> $collection */
public function __invoke(array $collection)
{
- return array_filter($collection, function (User $user) {
- return $user->getAge() >= $this->ageThreshold;
- });
+ return $collection;
}
}
public function test_it_filters_adults(): void
{
$users = [
User::withAge(20),
];
$filter = new UserAgeFilter();
$filteredUsers = $filter($users);
assertCount(1, $filteredUsers);
}
public function test_it_filters_adults(): void
{
$users = [
+ User::withAge(15),
User::withAge(20),
];
$filter = new UserAgeFilter();
$filteredUsers = $filter($users);
assertCount(1, $filteredUsers);
}
class UserAgeFilter
{
// ...
/** @param array<int, User> $collection */
public function __invoke(array $collection)
{
return array_filter(
$collection,
function (User $user) {
- return $user->getAge() >= $this->ageThreshold;
+ return $user->getAge() > $this->ageThreshold;
}
);
}
}
public function test_it_filters_adults(): void
{
$users = [
User::withAge(15),
User::withAge(20),
];
$filter = new UserAgeFilter();
$filteredUsers = $filter($users);
assertCount(1, $filteredUsers);
}
public function test_it_filters_adults(): void
{
$users = [
User::withAge(15),
+ User::withAge(18),
User::withAge(20),
];
$filter = new UserAgeFilter();
$filteredUsers = $filter($users);
- assertCount(1, $filteredUsers);
+ assertCount(2, $filteredUsers);
}
public function deactivateInactiveUsers(): void
{
$users = $this->userRepository->findInactiveUsers();
foreach ($users as $user) {
$user->deactivate();
- $this->entityManager->persist($user);
}
$this->entitymanager->flush();
}
- $a = $b * (-1);
+ $a = $b / (-1);
- $a = $b * $c;
+ $a = $b / $c;
- $result = [$a, $b] + [$c, $d];
+ $result = [$a, $b] - [$c, $d];
infection --git-diff-filter=AM --git-diff-base=master
Test # Mutation # |
1 | 2 | 3 | Result |
---|---|---|---|---|
1 | ✅ 0.132s | ✅ 0.387s |
❌ 0.399s |
Killed |
2 | ✅ | ✅ | ✅ | Escaped |
3 | ✅ | ❌ | Killed | |
4 | ✅ | ✅ | ❌ |
Test # Mutation # |
1 | 2 | 3 | Result |
---|---|---|---|---|
1 | ❌ 0.399s |
✅ 0.387s |
✅ 0.132s | Killed |
2 | ✅ | ✅ | ✅ | Escaped |
3 | ❌ | ✅ | Killed | |
4 | ❌ | ✅ | ✅ |
Test # Mutation # |
1 | 2 | 3 | Result |
---|---|---|---|---|
1 | ❌ 0.399s |
Killed | ||
2 | ✅ | ✅ | ✅ | Escaped |
3 | ❌ | Killed | ||
4 | ❌ |
- Time: 1m 49s. Memory: 0.07GB (first run)
+ Time: 38s. Memory: 0.07GB (second run)
$ infection --git-diff-filter=AM --threads=4 --show-mutations
$ infection --min-covered-msi=100
5) /srv/api/src/User/Status/StatusTransitioner.php:22 [M] PublicVisibility
--- Original
+++ New
@@ @@
- public function isAllowedTransitionTo(User $user, UserStatus $toStatus) : bool
+ protected function isAllowedTransitionTo(User $user, UserStatus $toStatus) : bool
{
if ($user->isDraft()) {
return $toStatus->isActive() || $toStatus->isDeleted();
Supported Test Frameworks:
Supported Coverage Drivers:
Supported PHP: 7.1+