diff --git a/.gitattributes b/.gitattributes
index 085a2fb..2108458 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -1,5 +1,6 @@
/.github/ export-ignore
/.phive/ export-ignore
+/src/Test/ export-ignore
/.editorconfig export-ignore
/.gitattributes export-ignore
/.gitignore export-ignore
@@ -9,6 +10,8 @@
/composer-require-checker.json export-ignore
/composer.lock export-ignore
/Makefile export-ignore
+/phpbench.json export-ignore
+/phpunit.xml export-ignore
/psalm-baseline.xml export-ignore
/psalm.xml export-ignore
/rector.php export-ignore
diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md
index 7ca8b1a..06eb960 100644
--- a/.github/CONTRIBUTING.md
+++ b/.github/CONTRIBUTING.md
@@ -46,6 +46,28 @@ make dependency-analysis
to run a dependency analysis.
+## Mutation Tests
+
+We are using [`infection/infection`](https://github.com/infection/infection) to ensure a minimum quality of the tests.
+
+Enable `Xdebug` and run
+
+```sh
+make mutation-tests
+```
+
+to run mutation tests.
+
+## Performance Tests
+
+We are using [`phpbench/phpbench`](https://github.com/phpbench/phpbench) to control the performance of our production code.
+
+```sh
+make performance-tests
+```
+
+to run performance tests.
+
## Refactoring
We are using [`rector/rector`](https://github.com/rectorphp/rector) to automatically refactor code.
@@ -94,6 +116,17 @@ to regenerate the baseline in [`../psalm-baseline.xml`](../psalm-baseline.xml).
:exclamation: Ideally, the baseline should shrink over time.
+## Tests
+
+We are using [`phpunit/phpunit`](https://github.com/sebastianbergmann/phpunit) to drive the development.
+
+Run
+
+```sh
+make tests
+```
+
+to run all the tests.
## Extra lazy?
@@ -103,7 +136,7 @@ Run
make
```
-to automatically refactor code, enforce coding standards, and run a static code analysis!
+to automatically refactor code, enforce coding standards, run a static code analysis, run tests, run performance tests, and run mutation tests!
## Help
diff --git a/.github/settings.yml b/.github/settings.yml
index 57ebbf4..8946ccf 100644
--- a/.github/settings.yml
+++ b/.github/settings.yml
@@ -15,11 +15,15 @@ branches:
required_status_checks:
checks:
- context: "Autoloader (8.2, locked)"
+ - context: "Code Coverage (8.2, locked)"
- context: "Coding Standards (8.2, locked)"
- context: "Dependency Analysis (8.2, locked)"
+ - context: "Mutation Tests (8.2, locked)"
+ - context: "Performance Tests (8.2, locked)"
- context: "Refactoring (8.2, locked)"
- context: "Security Analysis (8.2, locked)"
- context: "Static Code Analysis (8.2, locked)"
+ - context: "Tests (8.2, locked)"
strict: false
restrictions: null
diff --git a/.github/workflows/integrate.yaml b/.github/workflows/integrate.yaml
index d557ea6..0de4537 100644
--- a/.github/workflows/integrate.yaml
+++ b/.github/workflows/integrate.yaml
@@ -62,6 +62,62 @@ jobs:
- name: "Test autoloader for production"
run: "php autoloader.php"
+ code-coverage:
+ name: "Code Coverage"
+
+ runs-on: "ubuntu-latest"
+
+ strategy:
+ matrix:
+ php-version:
+ - "8.2"
+
+ dependencies:
+ - "locked"
+
+ steps:
+ - name: "Checkout"
+ uses: "actions/checkout@v3.3.0"
+
+ - name: "Set up PHP"
+ uses: "shivammathur/setup-php@2.24.0"
+ with:
+ coverage: "xdebug"
+ extensions: "none, ctype, dom, json, mbstring, phar, simplexml, tokenizer, xml, xmlwriter"
+ php-version: "${{ matrix.php-version }}"
+
+ - name: "Set up problem matchers for PHP"
+ run: "echo \"::add-matcher::${{ runner.tool_cache }}/php.json\""
+
+ - name: "Set up problem matchers for phpunit/phpunit"
+ run: "echo \"::add-matcher::${{ runner.tool_cache }}/phpunit.json\""
+
+ - name: "Determine composer cache directory"
+ uses: "ergebnis/.github/actions/composer/determine-cache-directory@1.8.0"
+
+ - name: "Cache dependencies installed with composer"
+ uses: "actions/cache@v3.2.6"
+ with:
+ path: "${{ env.COMPOSER_CACHE_DIR }}"
+ key: "php-${{ matrix.php-version }}-composer-${{ matrix.dependencies }}-${{ hashFiles('composer.lock') }}"
+ restore-keys: "php-${{ matrix.php-version }}-composer-${{ matrix.dependencies }}-"
+
+ - name: "Install ${{ matrix.dependencies }} dependencies with composer"
+ uses: "ergebnis/.github/actions/composer/install@1.8.0"
+ with:
+ dependencies: "${{ matrix.dependencies }}"
+
+ - name: "Collect code coverage with Xdebug and phpunit/phpunit"
+ env:
+ XDEBUG_MODE: "coverage"
+ run: "vendor/bin/phpunit --colors=always --configuration=phpunit.xml --coverage-clover=.build/phpunit/logs/clover.xml"
+
+ - name: "Send code coverage report to codecov.io"
+ uses: "codecov/codecov-action@v3.1.1"
+ with:
+ files: ".build/phpunit/logs/clover.xml"
+ token: "${{ secrets.CODECOV_TOKEN }}"
+
coding-standards:
name: "Coding Standards"
@@ -190,6 +246,98 @@ jobs:
- name: "Run maglnet/composer-require-checker"
run: ".phive/composer-require-checker check --ansi --config-file=$(pwd)/composer-require-checker.json"
+ mutation-tests:
+ name: "Mutation Tests"
+
+ runs-on: "ubuntu-latest"
+
+ strategy:
+ matrix:
+ php-version:
+ - "8.2"
+
+ dependencies:
+ - "locked"
+
+ steps:
+ - name: "Checkout"
+ uses: "actions/checkout@v3.3.0"
+
+ - name: "Set up PHP"
+ uses: "shivammathur/setup-php@2.24.0"
+ with:
+ coverage: "xdebug"
+ extensions: "none, ctype, dom, json, mbstring, phar, simplexml, tokenizer, xml, xmlwriter"
+ php-version: "${{ matrix.php-version }}"
+
+ - name: "Set up problem matchers for PHP"
+ run: "echo \"::add-matcher::${{ runner.tool_cache }}/php.json\""
+
+ - name: "Determine composer cache directory"
+ uses: "ergebnis/.github/actions/composer/determine-cache-directory@1.8.0"
+
+ - name: "Cache dependencies installed with composer"
+ uses: "actions/cache@v3.2.6"
+ with:
+ path: "${{ env.COMPOSER_CACHE_DIR }}"
+ key: "php-${{ matrix.php-version }}-composer-${{ matrix.dependencies }}-${{ hashFiles('composer.lock') }}"
+ restore-keys: "php-${{ matrix.php-version }}-composer-${{ matrix.dependencies }}-"
+
+ - name: "Install ${{ matrix.dependencies }} dependencies with composer"
+ uses: "ergebnis/.github/actions/composer/install@1.8.0"
+ with:
+ dependencies: "${{ matrix.dependencies }}"
+
+ - name: "Run mutation tests with Xdebug and infection/infection"
+ env:
+ XDEBUG_MODE: "coverage"
+ run: "vendor/bin/infection --ansi --configuration=infection.json --logger-github"
+
+ performance-tests:
+ name: "Performance Tests"
+
+ runs-on: "ubuntu-latest"
+
+ strategy:
+ matrix:
+ php-version:
+ - "8.2"
+
+ dependencies:
+ - "locked"
+
+ steps:
+ - name: "Checkout"
+ uses: "actions/checkout@v3.3.0"
+
+ - name: "Set up PHP"
+ uses: "shivammathur/setup-php@2.24.0"
+ with:
+ coverage: "none"
+ extensions: "none, ctype, dom, json, mbstring, phar, simplexml, tokenizer, xml, xmlwriter"
+ php-version: "${{ matrix.php-version }}"
+
+ - name: "Set up problem matchers for PHP"
+ run: "echo \"::add-matcher::${{ runner.tool_cache }}/php.json\""
+
+ - name: "Determine composer cache directory"
+ uses: "ergebnis/.github/actions/composer/determine-cache-directory@1.8.0"
+
+ - name: "Cache dependencies installed with composer"
+ uses: "actions/cache@v3.2.6"
+ with:
+ path: "${{ env.COMPOSER_CACHE_DIR }}"
+ key: "php-${{ matrix.php-version }}-composer-${{ matrix.dependencies }}-${{ hashFiles('composer.lock') }}"
+ restore-keys: "php-${{ matrix.php-version }}-composer-${{ matrix.dependencies }}-"
+
+ - name: "Install ${{ matrix.dependencies }} dependencies with composer"
+ uses: "ergebnis/.github/actions/composer/install@1.8.0"
+ with:
+ dependencies: "${{ matrix.dependencies }}"
+
+ - name: "Run performance tests with phpbench/phpbench"
+ run: "vendor/bin/phpbench run --config=phpbench.json src/Test/"
+
refactoring:
name: "Refactoring"
@@ -354,3 +502,53 @@ jobs:
- name: "Run vimeo/psalm"
run: "vendor/bin/psalm --config=psalm.xml --output-format=github --shepherd --show-info=false --stats --threads=4"
+
+ tests:
+ name: "Tests"
+
+ runs-on: "ubuntu-latest"
+
+ strategy:
+ matrix:
+ php-version:
+ - "8.2"
+
+ dependencies:
+ - "lowest"
+ - "locked"
+ - "highest"
+
+ steps:
+ - name: "Checkout"
+ uses: "actions/checkout@v3.3.0"
+
+ - name: "Set up PHP"
+ uses: "shivammathur/setup-php@2.24.0"
+ with:
+ coverage: "none"
+ extensions: "none, ctype, dom, json, mbstring, phar, simplexml, tokenizer, xml, xmlwriter"
+ php-version: "${{ matrix.php-version }}"
+
+ - name: "Set up problem matchers for PHP"
+ run: "echo \"::add-matcher::${{ runner.tool_cache }}/php.json\""
+
+ - name: "Set up problem matchers for phpunit/phpunit"
+ run: "echo \"::add-matcher::${{ runner.tool_cache }}/phpunit.json\""
+
+ - name: "Determine composer cache directory"
+ uses: "ergebnis/.github/actions/composer/determine-cache-directory@1.8.0"
+
+ - name: "Cache dependencies installed with composer"
+ uses: "actions/cache@v3.2.6"
+ with:
+ path: "${{ env.COMPOSER_CACHE_DIR }}"
+ key: "php-${{ matrix.php-version }}-composer-${{ matrix.dependencies }}-${{ hashFiles('composer.lock') }}"
+ restore-keys: "php-${{ matrix.php-version }}-composer-${{ matrix.dependencies }}-"
+
+ - name: "Install ${{ matrix.dependencies }} dependencies with composer"
+ uses: "ergebnis/.github/actions/composer/install@1.8.0"
+ with:
+ dependencies: "${{ matrix.dependencies }}"
+
+ - name: "Run tests with phpunit/phpunit"
+ run: "vendor/bin/phpunit --colors=always --configuration=phpunit.xml"
diff --git a/Makefile b/Makefile
index 49c82f8..9266095 100644
--- a/Makefile
+++ b/Makefile
@@ -1,11 +1,16 @@
.PHONY: it
-it: refactoring coding-standards security-analysis static-code-analysis autoloader ## Runs the refactoring, coding-standards, security-analysis, static-code-analysis, and autoloader targets
+it: refactoring coding-standards security-analysis static-code-analysis tests performance-tests mutation-tests autoloader ## Runs the refactoring, coding-standards, security-analysis, static-code-analysis, tests, performance-tests, mutation-tests, and autoloader targets
.PHONY: autoloader
autoloader: ## Dumps the autoloader for production and verifies that it does not include classes not intended for production
composer dump-autoload --no-dev --optimize
php autoloader.php
+.PHONY: code-coverage
+code-coverage: vendor ## Collects coverage from running unit tests with phpunit/phpunit
+ mkdir -p .build/phpunit
+ vendor/bin/phpunit --configuration=phpunit.xml --coverage-text
+
.PHONY: coding-standards
coding-standards: vendor ## Lints YAML files with yamllint, normalizes composer.json with ergebnis/composer-normalize, and fixes code style issues with friendsofphp/php-cs-fixer
yamllint -c .yamllint.yaml --strict .
@@ -21,6 +26,15 @@ dependency-analysis: phive vendor ## Runs a dependency analysis with maglnet/com
help: ## Displays this list of targets with descriptions
@grep -E '^[a-zA-Z0-9_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[32m%-30s\033[0m %s\n", $$1, $$2}'
+.PHONY: performance-tests
+performance-tests: vendor ## Runs performance tests with phpbench/phpbench
+ vendor/bin/phpbench run --config=phpbench.json src/Test/
+
+.PHONY: mutation-tests
+mutation-tests: vendor ## Runs mutation tests with infection/infection
+ mkdir -p .build/infection
+ vendor/bin/infection --configuration=infection.json
+
.PHONY: phive
phive: .phive ## Installs dependencies with phive
mkdir -p .build/phive/
@@ -47,6 +61,11 @@ static-code-analysis-baseline: vendor ## Generates a baseline for static code an
vendor/bin/psalm --config=psalm.xml --clear-cache
vendor/bin/psalm --config=psalm.xml --set-baseline=psalm-baseline.xml
+.PHONY: tests
+tests: vendor ## Runs tests with phpunit/phpunit
+ mkdir -p .build/phpunit
+ vendor/bin/phpunit --configuration=phpunit.xml
+
vendor: composer.json composer.lock
composer validate --strict
composer install --no-interaction --no-progress
diff --git a/composer.json b/composer.json
index 5ac1537..87c8d69 100644
--- a/composer.json
+++ b/composer.json
@@ -34,7 +34,10 @@
"autoload": {
"psr-4": {
"Localheinz\\OrganizingTestCodeInPhp\\": "src/"
- }
+ },
+ "exclude-from-classmap": [
+ "/src/Test/"
+ ]
},
"config": {
"allow-plugins": {
diff --git a/infection.json b/infection.json
new file mode 100644
index 0000000..e237036
--- /dev/null
+++ b/infection.json
@@ -0,0 +1,20 @@
+{
+ "ignoreMsiWithNoMutations": true,
+ "logs": {
+ "text": ".build/infection/infection-log.txt"
+ },
+ "minCoveredMsi": 100,
+ "minMsi": 100,
+ "phpUnit": {
+ "configDir": "."
+ },
+ "source": {
+ "directories": [
+ "src"
+ ],
+ "excludes": [
+ "Test/"
+ ]
+ },
+ "timeout": 10
+}
diff --git a/phpbench.json b/phpbench.json
new file mode 100644
index 0000000..4d5bd3e
--- /dev/null
+++ b/phpbench.json
@@ -0,0 +1,5 @@
+{
+ "$schema":"vendor/phpbench/phpbench/phpbench.schema.json",
+ "runner.bootstrap": "vendor/autoload.php",
+ "runner.file_pattern": "*.php"
+}
diff --git a/phpunit.xml b/phpunit.xml
new file mode 100644
index 0000000..da3edd6
--- /dev/null
+++ b/phpunit.xml
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+
+
+ src/Test/
+
+
+
diff --git a/psalm-baseline.xml b/psalm-baseline.xml
index 10b8be1..9527e5f 100644
--- a/psalm-baseline.xml
+++ b/psalm-baseline.xml
@@ -1,10 +1,8 @@
-
-
- equals
- fromString
- toString
-
+
+
+ ExampleBench
+
diff --git a/src/Test/ExampleBench.php b/src/Test/ExampleBench.php
new file mode 100644
index 0000000..65f3c26
--- /dev/null
+++ b/src/Test/ExampleBench.php
@@ -0,0 +1,29 @@
+expectException(ValueCanNotBeEmpty::class);
+
+ Example::fromString($value);
+ }
+
+ /**
+ * @dataProvider \Ergebnis\DataProvider\StringProvider::blank
+ */
+ public function testFromStringRejectsBlankValue(string $value): void
+ {
+ $this->expectException(ValueCanNotBeBlank::class);
+
+ Example::fromString($value);
+ }
+
+ public function testFromStringReturnsExample(): void
+ {
+ $value = self::faker()->word();
+
+ $example = Example::fromString($value);
+
+ self::assertSame($value, $example->toString());
+ }
+
+ public function testEqualsReturnsFalseWhenValueIsDifferent(): void
+ {
+ $faker = self::faker()->unique();
+
+ $one = Example::fromString($faker->word());
+ $two = Example::fromString($faker->word());
+
+ self::assertFalse($one->equals($two));
+ }
+
+ public function testEqualsReturnsTrueWhenValueIsSame(): void
+ {
+ $value = self::faker()->word();
+
+ $one = Example::fromString($value);
+ $two = Example::fromString($value);
+
+ self::assertTrue($one->equals($two));
+ }
+}
diff --git a/src/Test/Helper.php b/src/Test/Helper.php
new file mode 100644
index 0000000..9373b72
--- /dev/null
+++ b/src/Test/Helper.php
@@ -0,0 +1,41 @@
+
+ */
+ static $fakers = [];
+
+ if (!\array_key_exists($locale, $fakers)) {
+ $faker = Factory::create($locale);
+
+ $faker->seed(9001);
+
+ $fakers[$locale] = $faker;
+ }
+
+ return $fakers[$locale];
+ }
+}
diff --git a/src/Test/ValueCanNotBeBlankTest.php b/src/Test/ValueCanNotBeBlankTest.php
new file mode 100644
index 0000000..dee83a4
--- /dev/null
+++ b/src/Test/ValueCanNotBeBlankTest.php
@@ -0,0 +1,32 @@
+getMessage());
+ }
+}
diff --git a/src/Test/ValueCanNotBeEmptyTest.php b/src/Test/ValueCanNotBeEmptyTest.php
new file mode 100644
index 0000000..824e62a
--- /dev/null
+++ b/src/Test/ValueCanNotBeEmptyTest.php
@@ -0,0 +1,32 @@
+getMessage());
+ }
+}