While working through some of the later chapters in Dart for Hipsters, I found some code that really was not tested. Better yet, I know why it is untested: I don't know how. Yay! Fresh earth.
The Dart code in question is part of a whirlwind tour at the end of the book. Specifically, I have a very simple canvas demonstration:

The code draws a simple square, fills it, and attaches keyboard handlers to move the square around the screen. I already know that I cannot test the keyboard events. At least not yet. But I can still test something. In fact, if I had been testing, or at least performing simple code analysis, I would have noticed that I still have the old-style
on.keyDown.add() event handling: document.
on.
keyDown.
add((event) {
String direction;
// Listen for arrow keys
if (event.keyCode == 37) direction = 'left';
if (event.keyCode == 38) direction = 'up';
if (event.keyCode == 39) direction = 'right';
if (event.keyCode == 40) direction = 'down';
// ...
});I get everything working by switching to the new stream-based event handling: document.
onKeyDown.
listen((event) { /* ... */ });But I have nothing in place to prevent regressions should the language continue to evolve. This turns out to be fairly easy with Dart's unittest:
import 'package:unittest/unittest.dart';
import 'dart:html';
import '../web/main.dart' as Canvas;
main () {
group("[canvas]", (){
var canvas;
setUp((){
canvas = new CanvasElement();
document.body.append(canvas);
});
tearDown(()=> canvas.remove());
test("code runs", (){
expect(Canvas.main, returnsNormally);
});
});
}Here, I import the main.dart entry point for my canvas experiment and manually run the main() entry point function—expecting it to return normally. A bit of setup is required to add an expected <canvas> tag (along with matching teardown code). But really, very little is required for a pretty useful test.It even works from the command-line thanks to the magic of Chrome's
content_shell:➜ code git:(master) ✗ content_shell --dump-render-tree index.html CONSOLE MESSAGE: unittest-suite-wait-for-done CONSOLE MESSAGE: PASS: [canvas] code runs CONSOLE MESSAGE: CONSOLE MESSAGE: All 1 tests passed.If I intentionally break the code (by reverting my stream fix), I get the failure that would have helped me earlier:
➜ code git:(master) ✗ content_shell --dump-render-tree index.html
CONSOLE MESSAGE: unittest-suite-wait-for-done
CONSOLE MESSAGE: FAIL: [canvas] code runs
Expected: return normally
Actual: <Closure: () => dynamic from Function 'main': static.>
Which: threw NoSuchMethodError:<Class 'Events' has no instance getter 'keyDown'.
NoSuchMethodError : method not found: 'keyDown'
Receiver: Instance of 'Events'
Arguments: []>
package:unittest/src/simple_configuration.dart 140:7
package:unittest/src/simple_configuration.dart 15:28
package:unittest/src/expect.dart 117:9
package:unittest/src/expect.dart 75:29
../html5/test/test.dart 20:13My code event passes type analysis, which I perform automatically on any tested code in the book with dartanalyzer in my test_runner.sh:# ...
# Static type analysis
echo
echo "Static type analysis..."
results=$(dartanalyzer test.dart 2>&1)
echo "$results"
if [[ "$results" == *"[error]"* ]]
then
exit 1
fi
# ...That is all well and good, but is it possible to test some of the innards of the <canvas> element after the initial screen is drawn by main()? As can be seen from the screenshot above, the “player” starts near the top-left of the screen. In fact, the default is specified in the Player constructor:class Player {
int x, y;
int width = 20, height = 20;
Player() {
x = width~/2;
y = height~/2;
}
// ...
}The x-y origin of the starting position is then (20/2, 20/2), or (10, 10). Given that the width and height of the box is 20×20, the box ends at (30, 30). Since the “player” box is the last thing drawn in the <canvas> element, I can have an expectation that the point (30, 30) is in the current path: test("player is drawn", (){
Canvas.main();
var context = canvas.getContext('2d');
expect(context.isPointInPath(30, 30), isTrue);
});And that passes!PASS: [canvas] player is drawnIf I change the expectation to (31, 31), my test fails:
FAIL: [canvas] player is drawn
Expected: true
Actual: <false>So not only do I have a test that will catch small changes in the language that might affect my <canvas> code, I also have a test that will verify that my drawing works properly. And it even works from the command line. Day #878
No comments:
Post a Comment