Skip to content

Commit fb03842

Browse files
committed
chore: Minor copy edits
1 parent 6e3d6f9 commit fb03842

File tree

1 file changed

+52
-51
lines changed

1 file changed

+52
-51
lines changed

cookbooks/custom_formatter.rst

Lines changed: 52 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,41 @@
11
Writing a custom Behat formatter
22
================================
33

4-
How to write a custom formatter for Behat ?
4+
How to write a custom formatter for Behat?
55

66
Introdution
77
-----------
88

9-
Why a custom formatter ?
9+
Why a custom formatter?
1010
~~~~~~~~~~~~~~~~~~~~~~~~
1111

1212
Behat has three native formatters:
1313

14-
- **pretty**: the default formatter, that does print every line in green (in case of a successful test) or red (if it does fail),
14+
- **pretty**: the default formatter, which prints every line in green (if a test passes) or red (if it fails),
1515
- **progress**: print a "dot" for each test, and a recap of all failing tests at the end,
1616
- **junit**: outputs a `junit <https://junit.org/>`__ compatible XML file.
1717

1818
Those are nice, and worked for most of the cases. You can use the "progress" one for the CI, and the "pretty" for development for example.
1919

20-
But you might want to handle differently the output that Behat renders.
21-
In that cookbook, we will see how to implement a custom formatter for `reviewdog <https://github.com/reviewdog/reviewdog>`__,
20+
But you might want to handle differently the output that Behat renders.
21+
In this cookbook, we will see how to implement a custom formatter for `reviewdog <https://github.com/reviewdog/reviewdog>`__,
2222
a global review tool that takes input of linters or testers, and that can send "checks" on github, bitbucket or gitlab PR.
2323

2424
Reviewdog can handle `two types of input <https://github.com/reviewdog/reviewdog#input-format>`__:
2525

2626
- any stdin, coupled with an "errorformat" (a Vim inspired format that can convert text string to machine-readable errors),
2727
- a `"Reviewdog Diagnostic Format" <https://github.com/reviewdog/reviewdog/tree/48b25a0aafb8494e751387e16f729faee9522c46/proto/rdf>`__: a JSON with error data that reviewdog can parse.
2828

29-
In my case, I tried parsing behat's output with errorformat, but I do not know this language, and the multi-line behat output with "dots" didn't make it simple.
29+
In my case, I tried parsing Behat's output with errorformat, but I do not know this language, and the multi-line Behat output with "dots" didn't make it easy.
3030
So I decided to create a custom formatter for Behat.
3131

32-
This way, I will still have behat's human-readable stdout, and a JSON file written that reviewdog can understand.
32+
This way, I will still have Behat's human-readable stdout, and a JSON file written that reviewdog can understand.
3333

3434
Let's dive
3535
----------
3636

37-
Behat allows us to load "extensions", that can add features to the language. In fact, it is a core functionnality to implement PHP functions behind gherkin texts.
38-
Those extensions are simple classes, that are loaded in Behat.
37+
Behat allows us to load "extensions", that can add features to the language. In fact, it is a core functionality to implement PHP functions behind gherkin texts.
38+
Those extensions are just classes that are loaded by Behat to register configuration and features.
3939

4040
Behat is powered by Symfony: if you know it, you will already know the concepts under the hood, if you don't, that's not a problem and not required to create your extension.
4141

@@ -44,18 +44,18 @@ Anatomy of a formatter extension
4444

4545
A formatter extension requires three things to work:
4646

47-
- a class that "defines" the extension, to make you extension work with behat,
48-
- a "formatter", that can listen to behat events, and converts behat's tests result to anything you want,
49-
- an "output printer", that does write the converted data anywhere you want (mainly the stdout, a file or a directory).
47+
- a class that "defines" the extension, to make your extension work with Behat,
48+
- a "formatter", that can listen to Behat events, and converts Behat's tests result to anything you want,
49+
- an "output printer", that writes the converted data anywhere you want (mainly the stdout, a file or a directory).
5050

5151
Create the extension
5252
~~~~~~~~~~~~~~~~~~~~
5353

54-
Any behat extensions must implements ``Behat\Testwork\ServiceContainer\Extension``. Under the hood, it does implements Symfony ``CompilerPass``.
54+
Any Behat extensions must implement ``Behat\Testwork\ServiceContainer\Extension``. Under the hood, it implements Symfony ``CompilerPass``.
5555
It is a way to inject anything you want into Behat's kernel.
5656

57-
It our case, we need to load the "formatter" in behat's kernel, and tagged it as an output formatter.
58-
This way behat will allows our extension to be configured as a formatter. You can register multiple formatters with the same extension if you like.
57+
In our case, we need to load the "formatter" in Behat's kernel, and tag it as an output formatter.
58+
This way Behat will allow our extension to be configured as a formatter. You can register multiple formatters with the same extension if you like.
5959

6060
.. code:: php
6161
@@ -86,18 +86,18 @@ This way behat will allows our extension to be configured as a formatter. You ca
8686
// register the "Output printer" class
8787
$outputPrinterDefinition = $container->register(ReviewdogOutputPrinter::class);
8888
89-
// add some arguments. In that case, it will use behat's current working directory to write the output file, if not override
89+
// add some arguments. In this case, it will use Behat's current working directory to write the output file, if not override
9090
$outputPrinterDefinition->addArgument('%paths.base%');
9191
92-
// register the "ReviewdogFormatter" class in behat's kernel
92+
// register the "ReviewdogFormatter" class in Behat's kernel
9393
$formatterDefinition = $container->register(ReviewdogFormatter::class);
94-
95-
// add some arguments that will be called in the constructor.
96-
// That's not required but in my case I inject behats base path, to remove it from the absolute file path later, and the printer.
94+
95+
// add some arguments that will be called in the constructor.
96+
// This isn't required, but in my case I inject Behat's base path (to remove it from the absolute file path later) and the printer.
9797
$formatterDefinition->addArgument('%paths.base%');
9898
$formatterDefinition->addArgument($outputPrinterDefinition);
9999
100-
// tag the formatter as an "output.formatter", this way behat will add it to it's formatter list.
100+
// tag the formatter as an "output.formatter", this way Behat will add it to its formatter list.
101101
$formatterDefinition->addTag(OutputExtension::FORMATTER_TAG, ['priority' => 100]);
102102
}
103103
@@ -111,7 +111,7 @@ This way behat will allows our extension to be configured as a formatter. You ca
111111
Create the formatter
112112
~~~~~~~~~~~~~~~~~~~~
113113

114-
The formatter will listen to behat's events, and create output data depending on the type of event, the current state, etc.
114+
The formatter will listen to Behat's events, and create output data depending on the type of event, the current state, etc.
115115

116116
.. code:: php
117117
@@ -135,7 +135,7 @@ The formatter will listen to behat's events, and create output data depending on
135135
}
136136
137137
/**
138-
* setParameter will be called for each key given to the formatter in your behat.yml file.
138+
* setParameter will be called for each key given to the formatter in your behat.yml file.
139139
* We will see that later in the "integration".
140140
* In our case, the only allowed parameter is a "file_name" that must be a string : the JSON file that we will write.
141141
*/
@@ -160,13 +160,13 @@ The formatter will listen to behat's events, and create output data depending on
160160
public function getParameter($name) { }
161161
162162
/**
163-
* Our formatter is a Symfony EventSubscriber.
164-
* This method tells behat where we want to "hook" in the process.
163+
* Our formatter is a Symfony EventSubscriber.
164+
* This method tells Behat where we want to "hook" in the process.
165165
* Here we want to be called:
166166
* - at start, when the test is launched with the `BeforeExerciseCompleted::BEFORE` event,
167167
* - when a step has ended with the `StepTested::AFTER` event.
168-
*
169-
* There is a lot of other that can be found here: https://github.com/Behat/Behat/tree/2a3832d9cb853a794af3a576f9e524ae460f3340/src/Behat/Testwork/EventDispatcher/Event
168+
*
169+
* There are a lot of other events that can be found here in the Behat\Testwork\EventDispatcher\Event class
170170
*/
171171
public static function getSubscribedEvents()
172172
{
@@ -218,7 +218,7 @@ The formatter will listen to behat's events, and create output data depending on
218218
// get the relative path
219219
$path = str_replace($this->pathsBase . '/', '', $event->getFeature()->getFile() ?? '');
220220
221-
// do prepare the data that we will send to the printer…
221+
// prepare the data that we will send to the printer…
222222
$line = [
223223
'message' => $testResult->getException()?->getMessage() ?? 'Failed step',
224224
'location' => [
@@ -247,7 +247,7 @@ The formatter will listen to behat's events, and create output data depending on
247247
Create the output printer
248248
~~~~~~~~~~~~~~~~~~~~~~~~~
249249

250-
The latest file that we need to implement is the printer. In you case it's a simple class that can write lines to a file.
250+
The last file that we need to implement is the printer. In our case we need a single class that can write lines to a file.
251251

252252
.. code:: php
253253
@@ -278,15 +278,15 @@ The latest file that we need to implement is the printer. In you case it's a sim
278278
}
279279
280280
/**
281-
* outputPath is a special parameter that you can give to behat formatter under th key `output_path`
281+
* outputPath is a special parameter that you can give to any Behat formatter under the key `output_path`
282282
*/
283283
public function setOutputPath($path): void
284284
{
285285
$this->outputPath = $path;
286286
}
287287
288288
/**
289-
* The output path, defaults to behat's base path
289+
* The output path, defaults to Behat's base path
290290
*/
291291
public function getOutputPath(): string
292292
{
@@ -315,7 +315,8 @@ The latest file that we need to implement is the printer. In you case it's a sim
315315
}
316316
317317
/**
318-
* Behat can have mutliple verbosity level, you may want to handle it to display more informations.
318+
* Behat can have multiple verbosity levels, you may want to handle this to display more information.
319+
* These use the Symfony\Component\Console\Output\OutputInterface::VERBOSITY_ constants.
319320
* For reviewdog, I do not need that.
320321
*/
321322
public function setOutputVerbosity($level): void { }
@@ -328,7 +329,7 @@ The latest file that we need to implement is the printer. In you case it's a sim
328329
329330
/**
330331
* Writes message(s) to output stream.
331-
*
332+
*
332333
* @param string|array<string> $messages
333334
*/
334335
public function write($messages): void
@@ -342,10 +343,10 @@ The latest file that we need to implement is the printer. In you case it's a sim
342343
343344
/**
344345
* Writes newlined message(s) to output stream.
345-
*
346+
*
346347
* @param string|array<string> $messages
347348
*/
348-
349+
349350
public function writeln($messages = ''): void
350351
{
351352
if (!is_array($messages)) {
@@ -364,7 +365,7 @@ The latest file that we need to implement is the printer. In you case it's a sim
364365
}
365366
366367
/**
367-
* Called by the formatted when test starts
368+
* Called by the formatter when test starts
368369
*/
369370
public function removeOldFile(): void
370371
{
@@ -398,35 +399,35 @@ The latest file that we need to implement is the printer. In you case it's a sim
398399
Integration in your project
399400
~~~~~~~~~~~~~~~~~~~~~~~~~~~
400401

401-
You need to add the extension in you behat configuration file (default is ``behat.yml``) and configure it to use the formatter:
402+
You need to add the extension in your Behat configuration file (default is ``behat.yml``) and configure it to use the formatter:
402403

403404
.. code:: yaml
404405
405406
default:
406407
extensions:
407408
JDeniau\BehatReviewdogFormatter\ReviewdogFormatterExtension: ~
408-
409+
409410
formatters:
410411
pretty: true
411412
reviewdog: # "reviewdog" here is the "name" given in our formatter
412-
# output_path is optional and handled directy by behat
413+
# output_path is optional and handled directly by Behat
413414
output_path: 'build/logs/behat'
414415
# file_name is optional and a custom parameter that we inject into the printer
415416
file_name: 'reviewdog-behat.json'
416417
417418
Different output per profile
418419
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
419420

420-
You can active the extension only for a certain profile by specifying a profile in your command (ex: ``--profile=ci``)
421+
You can activate the extension only when you specify a profile in your command (ex: ``--profile=ci``)
421422

422-
For example if you want the pretty formatter by default, but both progress and reviewdog on your CI, you can configure it like that:
423+
For example if you want the pretty formatter by default, but both progress and reviewdog on your CI, you can configure it like this:
423424

424425
.. code:: yaml
425426
426427
default:
427428
extensions:
428429
JDeniau\BehatReviewdogFormatter\ReviewdogFormatterExtension: ~
429-
430+
430431
formatters:
431432
pretty: true
432433
@@ -439,26 +440,26 @@ For example if you want the pretty formatter by default, but both progress and r
439440
file_name: 'reviewdog-behat.json'
440441
441442
442-
Enjoy !
443+
Enjoy!
443444
-------
444445

445-
That's how you can write a simple custom behat formatter !
446+
That's how you can write a basic custom Behat formatter!
446447

447-
If you have much more complex logic, and you need to formatter to be more dynamic, behat do provide a FormatterFactory interface.
448-
You can see usage examples directly in `behat's codebase <https://github.com/Behat/Behat/tree/2a3832d9cb853a794af3a576f9e524ae460f3340/src/Behat/Behat/Output/ServiceContainer/Formatter>`__,
449-
but in a lot of cases, the simple formatter should work.
448+
If you have much more complex logic, and you need the formatter to be more dynamic, Behat do provide a FormatterFactory interface.
449+
You can see usage examples directly in `Behat's codebase <https://github.com/Behat/Behat/tree/2a3832d9cb853a794af3a576f9e524ae460f3340/src/Behat/Behat/Output/ServiceContainer/Formatter>`__,
450+
but in a lot of cases, something like this example should work.
450451

451-
Want to use reviewdog and the custom formatter yourself ?
452+
Want to use reviewdog and the custom formatter yourself?
452453
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
453454

454455
If you want to use the reviewdog custom formatter, you can find it on github: https://github.com/jdeniau/behat-reviewdog-formatter
455456

456-
There are other behat custom formatters in the wild, especially `BehatHtmlFormatterPlugin <https://github.com/dutchiexl/BehatHtmlFormatterPlugin>`__,
457-
that I did not test, but helped me understand how does behat formatter system works, and can output an HTML file that can help you understand why your CI is failing.
457+
There are other Behat custom formatters in the wild, especially `BehatHtmlFormatterPlugin <https://github.com/dutchiexl/BehatHtmlFormatterPlugin>`__.
458+
I did not test that, but it helped me understand how the Behat formatter system works, and it can output an HTML file that can help you understand why your CI is failing.
458459

459460

460461
About the author
461462
~~~~~~~~~~~~~~~~
462463

463464
Written by `Julien Deniau <https://julien.deniau.me>`__,
464-
originally posted as a blog post `on my blog <https://julien.deniau.me/posts/2024-01-24-custom-behat-formatter>`__.
465+
originally posted as a blog post `on my blog <https://julien.deniau.me/posts/2024-01-24-custom-behat-formatter>`__.

0 commit comments

Comments
 (0)