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