From 00227cf8ddef45f9c2c7cdf0c8d4a55c1d0cb69f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20M=C3=B6ller?= Date: Fri, 3 Mar 2023 09:34:49 +0100 Subject: [PATCH] Enhancement: Declare test code in subdirectories with separate configuration files --- .gitattributes | 1 + .github/CONTRIBUTING.md | 35 +++- .github/settings.yml | 4 + .github/workflows/integrate.yaml | 198 +++++++++++++++++++++++ Makefile | 21 ++- composer.json | 5 +- infection.json | 20 +++ psalm-baseline.xml | 10 +- src/Test/Performance/ExampleBench.php | 29 ++++ src/Test/Performance/phpbench.json | 5 + src/Test/Unit/ExampleTest.php | 82 ++++++++++ src/Test/Unit/ValueCanNotBeBlankTest.php | 32 ++++ src/Test/Unit/ValueCanNotBeEmptyTest.php | 32 ++++ src/Test/Unit/phpunit.xml | 46 ++++++ src/Test/Util/Helper.php | 41 +++++ 15 files changed, 552 insertions(+), 9 deletions(-) create mode 100644 infection.json create mode 100644 src/Test/Performance/ExampleBench.php create mode 100644 src/Test/Performance/phpbench.json create mode 100644 src/Test/Unit/ExampleTest.php create mode 100644 src/Test/Unit/ValueCanNotBeBlankTest.php create mode 100644 src/Test/Unit/ValueCanNotBeEmptyTest.php create mode 100644 src/Test/Unit/phpunit.xml create mode 100644 src/Test/Util/Helper.php diff --git a/.gitattributes b/.gitattributes index 085a2fb..b3ff422 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 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 2c82738..cfdb041 100644 --- a/.github/settings.yml +++ b/.github/settings.yml @@ -15,11 +15,15 @@ branches: required_status_checks: checks: - context: "Autoloader (8.3, locked)" + - context: "Code Coverage (8.3, locked)" - context: "Coding Standards (8.3, locked)" - context: "Dependency Analysis (8.3, locked)" + - context: "Mutation Tests (8.3, locked)" + - context: "Performance Tests (8.3, locked)" - context: "Refactoring (8.3, locked)" - context: "Security Analysis (8.3, locked)" - context: "Static Code Analysis (8.3, locked)" + - context: "Tests (8.3, locked)" strict: false restrictions: null diff --git a/.github/workflows/integrate.yaml b/.github/workflows/integrate.yaml index 4780948..35ccc64 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.3" + + 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=src/Test/Unit/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.3" + + 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.3" + + 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=src/Test/Performance/phpbench.json src/Test/Performance/" + 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.3" + + 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 unit tests with phpunit/phpunit" + run: "vendor/bin/phpunit --colors=always --configuration=src/Test/Unit/phpunit.xml" diff --git a/Makefile b/Makefile index 8d091be..55ce5c4 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=src/Test/Unit/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=src/Test/Performance/phpbench.json src/Test/Performance/ + +.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 unit tests with phpunit/phpunit + mkdir -p .build/phpunit + vendor/bin/phpunit --configuration=src/Test/Unit/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 0dba21a..56f7905 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..a8be984 --- /dev/null +++ b/infection.json @@ -0,0 +1,20 @@ +{ + "ignoreMsiWithNoMutations": true, + "logs": { + "text": ".build/infection/infection-log.txt" + }, + "minCoveredMsi": 100, + "minMsi": 100, + "phpUnit": { + "configDir": "src/Test/Unit/" + }, + "source": { + "directories": [ + "src" + ], + "excludes": [ + "Test/" + ] + }, + "timeout": 10 +} diff --git a/psalm-baseline.xml b/psalm-baseline.xml index d4df090..b271e09 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -1,10 +1,8 @@ - - - equals - fromString - toString - + + + ExampleBench + diff --git a/src/Test/Performance/ExampleBench.php b/src/Test/Performance/ExampleBench.php new file mode 100644 index 0000000..74b8ae3 --- /dev/null +++ b/src/Test/Performance/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/Unit/ValueCanNotBeBlankTest.php b/src/Test/Unit/ValueCanNotBeBlankTest.php new file mode 100644 index 0000000..fb4c0ab --- /dev/null +++ b/src/Test/Unit/ValueCanNotBeBlankTest.php @@ -0,0 +1,32 @@ +getMessage()); + } +} diff --git a/src/Test/Unit/ValueCanNotBeEmptyTest.php b/src/Test/Unit/ValueCanNotBeEmptyTest.php new file mode 100644 index 0000000..9e787d5 --- /dev/null +++ b/src/Test/Unit/ValueCanNotBeEmptyTest.php @@ -0,0 +1,32 @@ +getMessage()); + } +} diff --git a/src/Test/Unit/phpunit.xml b/src/Test/Unit/phpunit.xml new file mode 100644 index 0000000..7f7a4bf --- /dev/null +++ b/src/Test/Unit/phpunit.xml @@ -0,0 +1,46 @@ + + + + + + + + + ../../ + + + . + ../Performance/ + ../Util/ + + + + + . + + + diff --git a/src/Test/Util/Helper.php b/src/Test/Util/Helper.php new file mode 100644 index 0000000..63afca0 --- /dev/null +++ b/src/Test/Util/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]; + } +}