mirror of
				https://github.com/flame-engine/flame.git
				synced 2025-11-04 04:47:13 +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
 | 
						|
 | 
						|
   ```console
 | 
						|
   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.
 |