[Flutter] Test coverage

2022-05-04 hit count image

Let's see how to create the test coverage and check where the test code doesn't cover via the test coverage in Flutter.

Outline

When you develop the app with Flutter, you write the test code naturally. At this time, to check whether the test code covers the whole code or not, we see the Test coverage.

In this blog post, I will introduce how to check the test coverage in Flutter.

Test coverage

Flutter basically provides the test coverage feature. So, you can execute the following command to generate the test coverage.

flutter test --coverage

After executing the command, you will see the test code is executed like the following and the ./coverage/lcov.info file is generated.

00:07 +27: All tests passed!

When you open the ./coverage/lcov.info file, you can see the result like the below.

SF:lib/controllers/splash_controller.dart
DA:6,6
DA:11,3
DA:13,1
DA:15,2
DA:18,2
DA:19,2
LF:6
LH:6
...

Install lcov

We’ve seen that Flutter provides the test coverage feature basically, and the test coverage result is generated in the ./coverage/lcov.info file. However, the contents of the test coverage is not easy to understand.

Here, when you use lcov, you can transform the lcov.info file generated by the test coverage to the HTML file.

You can install lcov by the following command.

  • macOS: brew install lcov
  • Windows: choco install lcov

Transform HTML

Now, execute the following command to transform the ./coverage/lcov.info file to the HTML file by lcov.

genhtml coverage/lcov.info -o coverage/html

And then, you can see the HTML file is generaged like the below.

Writing .css and .png files.
Generating output.
Processing file lib/main.dart
Processing file lib/controllers/splash_controller.dart
Processing file lib/controllers/app_web_view_controller.dart
Processing file lib/controllers/home_screen_controller.dart
Processing file lib/screens/home_screen.dart
Processing file lib/widgets/drawer_sub_menu.dart
Processing file lib/widgets/app_drawer.dart
Processing file lib/widgets/app_drawer_header.dart
Processing file lib/widgets/app_header.dart
Processing file lib/widgets/drawer_group_title.dart
Processing file lib/widgets/right_icon_menu_button.dart
Processing file lib/widgets/drawer_bottom_button.dart
Processing file lib/widgets/drawer_main_menu.dart
Writing directory view page.
Overall coverage rate:
  lines......: 100.0% (184 of 184 lines)
  functions..: no data found

Also, you can see the result files are generated in the ./coverage/html directory.

Check

Next, let’s try to open the ./coverage/html/index.html file. In my case, I’m a macOS user, so I can use the following command to open the file.

open ./coverage/html/index.html

When you open the file, you can see the test coverage by file like the below.

Flutter - test coverage file list

When you click the link to open the file, you can see the details of the test coverage like the below.

Flutter - test coverage details

.gitignore

You don’t need to keep the lcov.info file, which is generated by the Flutter test coverage, and the HTML files, which are generated by the lcov command, in the Git version controller.

So, open the .gitignore file and add the coverage directory like the below to it for ignoring the test coverage file and related HTML files.

...
coverage/
...

bin/coverage.dart

Until now, we’ve seen how to generate the test coverage in Flutter, and how to transform the result to the HTML files by lcov. And we’ve seen the test coverage result by opening the HTML file.

In my case, I make the ./bin/coverage.dart file and modify it like the below to execute all commands by one command.

// ignore_for_file: avoid_print
import 'dart:io';

void main(List<String> arguments) async {
  await execute('flutter test --coverage');
  await execute('genhtml coverage/lcov.info -o coverage/html');
  await execute('open coverage/html/index.html');
}

Future<void> execute(String cmd, {String? dir, bool skipError = false}) async {
  print(cmd + (dir != null ? ' [on $dir]' : ''));

  var args = cmd.split(' ');
  var command = args.first;
  var options = args.length > 1
      ? args.getRange(1, args.length).toList()
      : [] as List<String>;

  var result = await Process.run(
    command,
    options,
    workingDirectory: dir,
  );

  print(result.stdout);
  if (!skipError && result.stderr != '') {
    throw Exception(result.stderr);
  }
}

If you create the file like above, you can execute the following command to execute all commands.

dart run :coverage

As I mentioned, I’m a macOS user, so if you’re a Windows user, you need to modify the following commands for Windows.

void main(List<String> arguments) async {
  await execute('flutter test --coverage');
  await execute('genhtml coverage/lcov.info -o coverage/html');
  await execute('open coverage/html/index.html');
}

Completed

Done! we’ve seen how to generate the test coverage in Flutter. We can’t say the test code covers all codes when the test coverage is 100%, but we can check which codes are not tested by the test coverage. So, it’s very helpful to write the test code.

Was my blog helpful? Please leave a comment at the bottom. it will be a great help to me!

Posts