mirror of
https://github.com/flame-engine/flame.git
synced 2025-10-28 03:16:09 +08:00
177 lines
5.9 KiB
Markdown
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.
|