Files
flame/doc/development/testing_guide.md
Jithuraj 306ad32052 docs: Update content types of sphinx code snippets (#2519)
Update content types of some sphinx code snippets.
2023-04-25 16:28:54 +02:00

177 lines
5.9 KiB
Markdown

# Writing tests
- All new functionality must be tested, if at all possible. When fixing a bug, tests must be added
to ensure that this bug would not reappear in the future.
- Run `melos run coverage` to execute all tests in the "coverage" mode. The results will be saved
in the `coverage/index.html` file, which can be opened in a browser. Try to achieve 100% coverage
for any new functionality added.
- Every source file should have a corresponding test file, with the `_test` suffix. For example,
if you're making a `SpookyEffect` and the source file is `src/effects/spooky_effect.dart`, then
the test file should be `test/effects/spooky_effect_test.dart` mirroring the source directory.
- The test file should contain a `main()` function with a single `group()` whose name matches the
name of the class being tested. If the source file contains multiple public classes, then each of
them should have its own group. For example:
```dart
void main() {
group('SpookyEffect', () {
// tests here
});
}
```
- For a larger class, multiple groups can be created inside the top-level group, allowing to
navigate the test suite easier. The names of the nested groups should be capitalized.
- The names of the individual tests should normally start with a lowercase.
- Often, you would need to define multiple helper classes to run the tests. Such classes should be
private (start with an underscore), and placed at the end of the file. The reason for this is that
whenever some test breaks, the first thing one needs to do is to go into the test file and run all
the tests. Having the `main()` function at the top of the file makes this process much easier.
## Types of tests
### Simple tests
```dart
test('the name of the test', () {
expect(...);
});
```
This is the simplest kind of test available, and also the fastest. Use these tests for checking
some classes/methods that can function in isolation from the rest of the Flame framework.
### FlameGame tests
It is very common to want to have a `FlameGame` instance inside a test, so that you can add some
components to it and verify various behaviors. The following approach is recommended:
```dart
testWithFlameGame('the name of the test', (game) async {
game.add(...);
await game.ready();
expect(...);
});
```
Here the `game` instance that is passed to the test body is a fully initialized game that behaves
as if it was mounted to a `GameWidget`. The `game.ready()` method waits until all the scheduled
components are loaded and mounted to the component tree.
The time within the `game` can be advanced with `game.update(dt)`.
If you need to have a custom game inside this test (say, a game with some mixin), then use
```dart
testWithGame<_MyGame>(
'the name of the test',
_MyGame.new,
(game) async {
// test body...
},
);
```
### Widget tests
Sometimes having a "naked" `FlameGame` is insufficient, and you want to have access to the Flutter
infrastructure as well. That is, to have a game mounted into a real `GameWidget` embedded into an
actual Flutter framework. In such cases, use
```dart
testWidgets('test name', (tester) async {
final game = _MyGame();
await tester.pumpWidget(GameWidget(game: game));
await tester.pump();
await tester.pump();
// At this point the game is fully initialized, and you can run your checks
// against it.
expect(...);
// Equivalent to game.update(0)
await tester.pump();
// Advances in-game time by 20 milliseconds
await tester.pump(const Duration(milliseconds: 20));
});
```
There are some additional methods available on the `tester` controller, for example in order to
simulate taps, or drags, or key presses.
### Golden tests
These tests verify that things render as intended. The process of creating a golden test is
simple:
1. Write the test, using the following template:
```dart
testGolden(
'the name of the test',
(game) async {
// Set up the game by adding the necessary components
// You can add `expect()` checks here too, if you want to
},
size: Vector2(300, 200),
goldenFile: '.../_goldens/my_test_file.png',
);
```
Here the `size` parameter determines the size of the game canvas and of the output image. The
`goldenFile` parameter is the name of the file where you want to store the "golden" results. This
should be a relative path to the `test/_goldens` directory, starting from your test file.
2. Run
```shell
flutter test --update-goldens
```
this would create the golden file for the first time. Open the file to verify that it renders
exactly as you intended. If not, then delete the file and go back to step 1.
3. Subsequent runs of `flutter test` will check whether the output of the golden test matches the
saved golden file. If not, Flutter will save the image-diff files into the `failures/` directory
where your test is located.
```{note}
Avoid using text in your golden tests -- it does not render reliably across
different platforms, due to font discrepancies and differences in
anti-aliasing algorithms.
```
### Random tests
These are the tests that use a random number generator in order to construct a randomized input and
then check its correctness. Use as follows:
```dart
testRandom('test name', (Random random) {
// Use [random] to generate random input
});
```
You can add `repeatCount: 1000` parameter to run this test the specified number of times, each one
with a different seed. It is useful to run a high `repeatCount` when developing the test, to ensure
that it doesn't break. However, when submitting the test to the main repository, avoid repeatCounts
higher than 10.
If the test breaks at some particular seed, then that seed will be shown in the test output. Add it
as the `seed: NNN` parameter to your test, and you'll be able to run it for the same seed as long
as you need until the test is fixed. Do not leave the `seed:` parameter when submitting your code,
as it defeats the purpose of having the test randomized.