49dc34715e
Fixes https://github.com/dart-lang/sdk/issues/61915 I add documentation to a few APIs, and primarily document `newPackage` and `PackageBuilder.addFile`, which already have sufficient doc comments. Change-Id: Ice088c2d76604ccb991d895a1e4bb408c492b963 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/467240 Reviewed-by: Brian Wilkerson <brianwilkerson@google.com> Commit-Queue: Samuel Rawlins <srawlins@google.com> Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
204 lines
7.1 KiB
Markdown
204 lines
7.1 KiB
Markdown
# Testing rules
|
|
|
|
The [`analyzer_testing`][] package provides an API for testing analysis rules.
|
|
Tests can be written concisely, encouraging the plugin author to write test
|
|
cases with good coverage of possible Dart syntax, and the analysis rules
|
|
themselves.
|
|
|
|
## The test class
|
|
|
|
Analysis rule tests that are written with the [`analyzer_testing`][] package's
|
|
support use a class hierarchy to specify shared variables, helper methods, and
|
|
set-up and tear-down code. This is all based on the [`test_reflective_loader`][]
|
|
package. Here is the basic structure:
|
|
|
|
|
|
```dart
|
|
import 'package:analyzer_testing/analysis_rule/analysis_rule.dart';
|
|
import 'package:my_rule/src/rules/my_rule.dart';
|
|
import 'package:test_reflective_loader/test_reflective_loader.dart';
|
|
|
|
@reflectiveTest
|
|
class MyRuleTest extends AnalysisRuleTest {
|
|
@override
|
|
void setUp() {
|
|
rule = MyRule();
|
|
super.setUp();
|
|
}
|
|
|
|
// Test cases go here.
|
|
}
|
|
```
|
|
|
|
This test file can be written anywhere in the `test` directory of the plugin
|
|
package, maybe at `test/my_rule_test.dart`.
|
|
|
|
In this code, we are testing the `my_rule` analysis rule built in
|
|
[writing rules][], which reports any time an 'await expression' is found.
|
|
|
|
This structure is different from the classic test structure used when writing
|
|
tests with the `test` package, in which all tests are declared in anonymous
|
|
closures passed to the `group` and `test` functions. Let's examine the
|
|
components of the `MyRuleTest` class.
|
|
|
|
* `class MyRuleTest extends AnalysisRuleTest` - The test class uses
|
|
`AnalysisRuleTest`, from the `analyzer_testing` package, as a base.
|
|
`AnalysisRuleTest` provides common functionality like `assertDiagnostics` and
|
|
`newFile`.
|
|
* `void setUp` - Override this method to provide some set-up code that is
|
|
executed before each test. This method must call `super.setUp()`. This method
|
|
is where we instantiate the analysis rule that we are testing:
|
|
`rule = MyRule();`.
|
|
|
|
## The test cases
|
|
|
|
The individual test cases are declared as instance methods of this class. Each
|
|
method whose name starts with `test_` is registered as a test case. See the
|
|
[`test_reflective_loader`][] package's documentation for more details.
|
|
|
|
```dart
|
|
@reflectiveTest
|
|
class MyRuleTest extends AnalysisRuleTest {
|
|
// ...
|
|
|
|
void test_has_await() async {
|
|
await assertDiagnostics(
|
|
r'''
|
|
void f(Future<int> p) async {
|
|
await p;
|
|
}
|
|
''',
|
|
[lint(33, 5)],
|
|
);
|
|
}
|
|
|
|
void test_no_await() async {
|
|
await assertNoDiagnostics(
|
|
r'''
|
|
void f(Future<int> p) async {
|
|
// No await.
|
|
}
|
|
''');
|
|
}
|
|
}
|
|
```
|
|
|
|
Let's look at the APIs used in these test cases:
|
|
|
|
* `assertDiagnostics` - This is the primary assertion method used in analysis
|
|
rule tests. It allows us to assert which diagnostics are reported, for some
|
|
given Dart source code. The first argument is the source code, and the second
|
|
is a list of expected diagnostics, `ExpectedDiagnostic` objects. Generally,
|
|
`ExpectedDiagnostic` objects are not manually constructed. Instead, we use the
|
|
`lint()` function:
|
|
* `lint(33, 5)` - This utilitiy creates an expected diagnostic object
|
|
representing the analysis rule specified by the `analysisRule` getter, which
|
|
is expected at offset `33`, for a length of `5` characters.
|
|
* `assertNoDiagnostics` - This is a convenience utility that asserts that _no_
|
|
diagnostcs are reported for the given source code.
|
|
|
|
Most test cases can be written as simply as the two above, with a single call to
|
|
`assertDiagnostics` or `assertNoDiagnostics`.
|
|
|
|
Some test cases might involve code with compile-time errors, or warnings. (For
|
|
example, you might want to verify that the analysis rule does not report when
|
|
certain error conditions are present, so that the user can focus on fixing the
|
|
error conditions, and not on spurious lint diagnostics.) Here is an example:
|
|
|
|
```dart
|
|
void test_has_await_in_non_async() async {
|
|
await assertDiagnostics(
|
|
r'''
|
|
void f(Future<int> p) {
|
|
await p;
|
|
}
|
|
''',
|
|
[
|
|
// No lint is reported with this error.
|
|
error(CompileTimeError.UNDEFINED_IDENTIFIER_AWAIT, 27, 5),
|
|
],
|
|
);
|
|
}
|
|
```
|
|
|
|
In this example, we assert that the only diagnostic reported for this code
|
|
is an `CompileTimeError.UNDEFINED_IDENTIFIER_AWAIT` error.
|
|
|
|
<!-- TODO(srawlins): In analyzer_testing: document writing multiple files with
|
|
`newFile`, then link to it here. -->
|
|
<!-- TODO(srawlins): In analyzer_testing: document writing a second package,
|
|
then link to it here. -->
|
|
|
|
## The entrypoint
|
|
|
|
All of the test code above comes together when we register the test class in the
|
|
test file's `main` function:
|
|
|
|
```dart
|
|
void main() {
|
|
defineReflectiveSuite(() {
|
|
defineReflectiveTests(MyRuleTest);
|
|
});
|
|
}
|
|
```
|
|
|
|
With this `main` function, tests can be run in the same way as class `test`
|
|
package tests. They can be run in the usual ways, such as using the IDE, or by
|
|
running `dart test` or `dart --enable-asserts test/my_rule_test.dart`.
|
|
|
|
[`analyzer_testing`]: https://pub.dev/packages/analyzer_testing
|
|
[writing rules]: https://github.com/dart-lang/sdk/blob/main/pkg/analysis_server_plugin/doc/writing_rules.md
|
|
[`test_reflective_loader`]: https://pub.dev/packages/test_reflective_loader
|
|
|
|
## Writing stub package sources
|
|
|
|
Often an analysis rule needs to understand if a type or an element (like a class
|
|
or a method) is a specific type/element from a specific library in a package.
|
|
For example, a rule might be concerned with the use of the `test` function
|
|
declared in the `test_core` package. In order to write tests for such a rule,
|
|
the test code needs to import something like
|
|
`'package:test_core/test_core.dart'`. In order to make such an import
|
|
meaningful, some stub code needs to be written so that, in fact, a `test`
|
|
function is made available by that import.
|
|
|
|
The [`AnalysisRuleTest`][] class offers a [`newPackage`][] method which supports
|
|
writing code in other packages. `newPackage` returns a `PackageBuilder`, which
|
|
is used to add individual library sources via its `addFile` method. For example,
|
|
to write the sources for a stub `test` function in a package named `test_core`,
|
|
you can:
|
|
|
|
```dart
|
|
class MyRuleTest extends AnalysisRuleTest {
|
|
@override
|
|
void setUp() {
|
|
newPackage('test_core')..addFile('lib/test_core.dart', r'''
|
|
void test(
|
|
Object? description,
|
|
dynamic body(), {
|
|
String? testOn,
|
|
Object? /*Timeout?*/ timeout,
|
|
Object? skip,
|
|
Object? tags,
|
|
Map<String, dynamic>? onPlatform,
|
|
int? retry,
|
|
Object? /*TestLocation?*/ location,
|
|
bool solo = false,
|
|
}) {}
|
|
''');
|
|
super.setUp();
|
|
}
|
|
}
|
|
```
|
|
|
|
Here are a few tips for writing stub package sources:
|
|
|
|
* `newPackage` needs to be called in `setUp`, before the call to `super.setUp`.
|
|
* For the static analysis purposes of testing analysis rules, it is unnecessary
|
|
to include function bodies (see the empty `test` body above).
|
|
* It is often not necessary to include all of the types which are needed to
|
|
write a type or an element, like a function signature (see the `location`
|
|
parameter above, which is typed as an `Object?` instead of a `TestLocation?`).
|
|
This can greatly simplify the stubs.
|
|
|
|
[`AnalysisRuleTest`]: https://pub.dev/documentation/analyzer_testing/latest/analysis_rule_analysis_rule/AnalysisRuleTest-class.html
|
|
[`newPackage`]: https://pub.dev/documentation/analyzer_testing/latest/analysis_rule_analysis_rule/AnalysisRuleTest/newPackage.html |