Flutter Engine
The Flutter Engine
|
The analyzer uses the test_reflective_loader
package for most of its tests. The test_reflective_loader
package uses the test
package to actually run the tests, but it provides a JUnit style mechanism for writing the tests.
The tests, as expected, are in the top-level test
directory. The structure of the directories within the test
directory should match the structure of the lib
directory whenever possible. The tests are in files whose name ends with _test.dart
. This convention is used by the test runner on the bots to identify the files to be run, so a failure to follow this convention will cause the tests to not be run on the bots.
For convenience, every directory in the test
directory (including the test
directory) contains a file named test_all.dart
. That file isn't run on the bots, but can be run manually in order to run all the tests in the containing directory and all subdirectories.
Within a test file, the tests are defined in one or more classes. By convention, the class name should end with Test
. The class must be annotated with @reflectiveTest
.
In order for the file to be executable, it must define a main
method that looks something like the following, with one invocation of defineReflectiveTests
for every reflective test class in the file:
When the tests are run the test loader will reflect on the specified class (CompilationUnitImplTest
in the example above) to find all the zero parameter instance methods whose name starts with 'test_'. These methods should have a return type of either void
or Future<void>
.
There are a couple of useful annotations defined for test methods.
@FailingTest()
to indicate that it is expected to fail when run. This allows us to commit tests for bugs before working on a fix for those bugs. The constructor has some optional parameters that allow you to specify the reason for the failure and an issue URL.@SkippedTest()
if the test should not be run. The constructor has the same optional parameters for specifying the reason and an issue URL.@soloTest
to cause those tests to be the only tests that are run.Defining tests as methods on a class rather than as invocations of the test
and group
functions has the advantage that we can define common test utilities and share them across a large number of tests. To do that without classes would be much harder.
But this style of test also has the disadvantage that we can't organize the tests into groups. In order to overcome this disadvantage we have adopted an uncommon naming convention for the test methods that combines the camel case and snake case conventions.
Let's start with an example. Assume that we have a class named ToSourceVisitor
that is a visitor with a separate method for every class of AST node, and that we want to test every method. Using group
and test
, we'd probably create a group for the class, then a subgroup for each visit method. For nodes with optional children, such as a list literal (where the const
modifier and type arguments are both optional) you might have a group for literals with or without the type arguments, and then tests both with and without the modifier. In other words, you might end up with a structure like this:
In out tests, the top-level group is replaced by the class, which would be named ToSourceVisitorTest
. The methods would all start with test
, and each group's name would be converted to a camelCase identifier with groups being separated with an underscore. So the equivalent to the code above would be:
Most of our tests take a small piece of Dart code and test the behavior of some piece of functionality. In most cases the Dart code is required to be a whole compilation unit, though there are a few tests where only a snippet of code is required. We have a couple of conventions that, while not strictly enforced, are generally followed.
Test code generally appears in a multi-line string, even when it would fit on a single line, with the text fully left justified, and with the closing quotes on a separate line. For example:
Test code should be kept as short as possible. Use short names and don't include code that isn't required in order to test what's being tested.
Test code should generally follow best practices unless the deviation from best practices is a necessary part of what's being tested.
Don't use a name with a special meaning, like main
, unless it's important that you do so for the test.