TDD with the bird framework
Test Driven Development, or TDD, is the process of creating software by first creating a failing test and then adding enough code to pass the test. With this process, by the end of the completion of the specific project. The developer will have a robust test suite and code that should have no, or minimal, bugs.
Whenever I start a project with a framework. I like to get an idea of what kind of testing suite is popular and how to use it. TDD can be pretty tedious sometimes, but it is always worth it. Since our capstone project will be created in Flutter, I decided it was time to visit the official Flutter documentation on Flutter’s testing framework and get up to speed on what the testing framework and environment is all about.
Link: https://docs.flutter.dev/cookbook/testing/unit/introduction
Step 1: Add the Test Dependency
The first step is to add the testing dependency to pubspec.yaml. (Flutters dependency tracking file). There will be two dependencies that I’ll add to the file. (If you run flutter create [appName] to create a project, chances are flutter_test is already installed.)
There are a variety of options to include outside frameworks to a flutter project.
test: https://pub.dev/packages/test
flutter_test: https://api.flutter.dev/flutter/flutter_test/flutter_test-library.html
The package test is needed for background Flutter dependencies, while flutter_test is necessary to perform testing on Widgets, which are the primary components of the Flutter UI. There is a variety of ways to install the dependency depending on the needs of the application. Due to some stability issues I always recommend what the pub.dev documentation says about installation.
Step 2: Create a Test File
The next step is to create a directory where the tests will go. Again, if the command line was used to create the app this folder is already created. If not, within the main directory of the project where the pubspec.yaml resides. There should be a directory called test.
Within test is where the testing files should be.
main_directory
- lib
- counter.dart
- test
- counter_test.dart
It is recommended to using naming conventions that make sense for both file and test file names.
Step 3: Set up test class
I’m following along with the tutorial, so I made the following dart class to test. I named the file to test ‘counter.dart’ and added the below code. It’s a simple counter class made in dart that will increment or decrement depending on the method selected. Again the test package is more for testing Dart classes.
class Counter {
int value = 0;
void increment() => value++;
void decrement() => value--;
}
Step 4: Write a test (And Grouping Tests)
The next step is to create a test file and test class so that we can test our new counter app. I have added a file called ‘counter_test.dart’ to the test directory with the below code.
import 'package:test/test.dart';
import 'package:main_app/counter.dart';
void main() {
test('Counter value should be incremented', () {
final counter = Counter();
counter.increment();
expect(counter.value, 1);
});
}
A breakdown of the code. I import two packages the test package and the counter.dart file. Like Flutter apps, the test file needs to have a starting point of a function called main(). Within the main function I use the test() function to create my test. It takes two values, a string for the message and a closure for the test code. Within the closure the object counter is created from the Counter() class and I increment the value. At the end of the test I call my expect() which takes the counter.value and the comparative operator. Since counter starts at 0, calling increment() once will increase the value to 1 making a passing test.
Usually, there are more than one tests that need to be run. It is recommended according to the Flutter documentation that multiple tests that make sense to run together should be encased in a group.
import 'package:test/test.dart';
import 'package:main_app/counter.dart';
void main() {
group('Counter', () {
test('value should start at 0', (){
expect(Counter().value, 0);
});
test('Counter value should be incremented', () {
final counter = Counter();
counter.increment();
expect(counter.value, 1);
});
test('value should be decremented', () {
final counter = Counter();
counter.decrement();
expect(counter.value, -1);
});
});
}
Above I have great code coverage for my Counter() class and all the tests involving the class are included in the same group. This improves cod readability.
Step 5: Run Tests
Running the tests are simple. In the command line run:
flutter test test/counter_test.dart
Step 7: Integrate test file into Continuous Integration environment
The great part about the Flutter testing environment is that with one small keyword all tests will be run. All that needs to be added is ‘flutter test’ and it will parse all the test files within the test folder and perform them. An example set up with flutter test added to the continuous integration *.yml file.
name: Flutter Craigslist CI
on:
push:
branches:
- main
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-java@v2
with:
distribution: 'zulu'
java-version: '11'
- uses: subosito/flutter-action@v2
with:
channel: 'stable'
- run: flutter --version
- run: flutter analyze
- run: flutter pub get
- run: flutter test
- run: flutter build apk
- run: flutter build appbundle