Slide 1

Slide 1 text

Testing Symfony/Console applications with PHPUnit Compiled by Raphael Stolt

Slide 2

Slide 2 text

The system under test The business logic of the SUT ( ) is the validation, creation, and modification of the .gitattributes file of any given software 📦. http://git.io/lean- package-validator

Slide 3

Slide 3 text

Purpose of a .gitattributes file The main* purpose of a .gitattributes file is to define which repository artifacts end up in a distributable package. A distributable package or Git archive is the one which is pulled into your project via package managers like Composer or pip . It helps package maintainers to reduce their 💨🌏 footprint by preventing needless file transfers.

Slide 4

Slide 4 text

Anatomy of a .gitattributes file Maybe located in the working directory of a given Git repository. * text=auto eol=lf *.sqlite diff=sqlite3 .editorconfig export-ignore .gitattributes export-ignore .github/ export-ignore .gitignore export-ignore .php-cs-fixer.php export-ignore box.json.dist export-ignore CHANGELOG.md export-ignore example/ export-ignore LICENSE.md export-ignore phpstan.neon.dist export-ignore phpunit.xml.dist export-ignore README md export ignore

Slide 5

Slide 5 text

Scenarios to test The produced exit code of the tool under test is very important, as it's intended to be hooked into CI pipelines via . Over time additional features emerged like showing the diff between the expected and actual .gitattributes content; or the injection of glob pattern to utilise it for packages outside of the 🐘 ecosystem. GitHub Actions

Slide 6

Slide 6 text

Classic PHPUnit test setup $ cat phpunit.xml.dist tests/

Slide 7

Slide 7 text

Classic PHPUnit test setup $ tree src/ tests/ src ├── Analyser.php ├── Archive │ └── Validator.php ├── Archive.php ├── Commands │ ├── InitCommand.php │ └── ValidateCommand.php ├── Exceptions │ ├── GitArchiveNotValidatedYet.php │ ├── GitattributesCreationFailed.php │ ├── GitHeadNotAvailable.php │ ├── GitNotAvailable.php │ ├── InvalidGlobPatternFile.php │ ├ InvalidGlobPattern php

Slide 8

Slide 8 text

Ways to approach testing a CLI 1. Trust your business logic tests and neglect the CLI input port to it 🚷

Slide 9

Slide 9 text

Ways to approach testing a CLI 2. Utilising PHP's good ol' exec ⛔ #[Test] public function executableIsAvailable(): void { $command = 'php bin/lean-package-validator'; \exec($command, $output, $returnValue); $this->assertStringStartsWith( 'Lean package validator', $output[1], 'Expected application name not present.' );

Slide 10

Slide 10 text

Ways to approach testing a CLI 3. Utilising the Symfony/Process component ⛔ #[Test] public function executableIsAvailable(): void { $command = 'php bin/lean-package-validator'; $process = Process::formShellCommandline($command); $process->run(); $this->assertStringStartsWith( 'Lean package validator', $process->getOutput(), 'Expected application name not present.' );

Slide 11

Slide 11 text

Ways to approach testing a Symfony/Console based CLI 4. Utilising Symfony/Console's CommandTester or ApplicationTester ✅ $ tree vendor/symfony/console/Tester vendor/symfony/console/Tester ├── ApplicationTester.php ├── CommandCompletionTester.php ├── CommandTester.php ├── Constraint │ └── CommandIsSuccessful.php └── TesterTrait.php 1 directory, 5 files

Slide 12

Slide 12 text

Ways to approach testing a Symfony/Console based CLI 4. Utilising Symfony/Console's CommandTester or ApplicationTester ✅ $ php ListCommandTesterPublicApi.php __construct(Command $command) execute(array $input, array $options = [])): int getDisplay(bool $normalize = false): string getErrorOutput(bool $normalize = false): string) getInput(): InputInterface getOutput(): OutputInterface getStatusCode(): int assertCommandIsSuccessful(): void setInputs(array $inputs): static

Slide 13

Slide 13 text

InitCommandTest /** * Set up test environment. */ protected function setUp(): void { $this->setUpTemporaryDirectory(); if (!\defined('WORKING_DIRECTORY')) { \define('WORKING_DIRECTORY', $this->temporaryDirectory); } $this->application = $this->getApplication(); } #[Test] public function verboseOutputIsAvailableWhenDesired(): void

Slide 14

Slide 14 text

ValidateCommandTest #[Test] public function gitattributesFileWithNoExportIgnoresShowsExpected { $analyserMock = Mockery::mock( 'Stolt\LeanPackage\Analyser[getGlobalGitignorePatterns]' ); $analyserMock->shouldReceive('getGlobalGitignorePatterns') ->once() ->withAnyArgs() ->andReturn([]); $expectedDisplay = " The present .gitattributes file is considered invalid. Would expect the following .gitattributes file content: # Auto detect text files and perform LF normalization

Slide 15

Slide 15 text

Executing the tests The lean-package-validator package has a dedicated Composer script named lpv:test . $ composer lpv:test > phpunit PHPUnit 10.3.5 by Sebastian Bergmann and contributors. Runtime: PHP 8.2.12 Configuration: /user/stolt/work/oss/lean- 📦-validator/phpunit.xml ..................................................... 63 / 121 ................................................. 121 / 121 Time: 00:00.981, Memory: 14.00 MB OK (121 tests, 204 assertions)

Slide 16

Slide 16 text

THE END Thank you and enjoy your green CLI integration tests. Questions?