Sunday, December 23, 2012

Testing Old-School Code in Dart

‹prev | My Chain | next›

I have gotten into a pretty good rhythm updating Dart for Hipsters. I am feverishly trying to update the book for Dart's recent M2 release (before they freaking announce an M3). This has mostly involved extracting code samples out into tested source files. But most of my work so far has involved non-browser based Dart code. Since most of the book involves building a client-side application, I really need a way to extract those code samples into testable chunks.

There are two problems that I foresee. First, the early parts of the book follow more of a JavaScript convention for building applications (to ease the reader into the Dart way). If I have my code in non-standard Dart locations, testing is more difficult. The second problem is that I need to keep the code sample in sync with various branches in the Dart Comics sample application. For sundry reasons, git sub-modules are not a possibility, so I see no other way than to manually copy code from Dart Comics into Dart for Hipsters.

Compounding my problems is Dart's current inability to perform headless testing. Whenever I make a change to the code in my Dart Functions chapter, I can run my tests and, in less than a second, know that everything still works. I am going to need to adjust my workflow for the browser-based chapters to reload a testing web page. That's not a huge deal, but I definitely look forward to headless testing.

I begin the book by introducing the following skeleton script:
import('dart:html');
import('dart:json');
main() {
  loadComics();
}
loadComics() {
  // Do stuff here
}
Since it does not actually do anything, there is little to test beyond that it compiles and returns without error. Since it is so simple, that seems like a good starting point. So, in my chapter's test directory, I create a local test page to hold my test output:
<html>
<head>
  <title>Your First Dart App Tests</title>
  <script type="application/dart" src="test.dart"></script>

  <script type="text/javascript">
    // start dart
    navigator.webkitStartDart();
  </script>
</head>

<body>
<h1>Test!</h1>

</body>
</html>
In the referenced test.dart file, I adapt the convention that I have been using in my pure Dart tests. I import a series of tests that describe a particular aspect of the book's discussion and run the tests:
import 'package:unittest/html_enhanced_config.dart';

import 'skeleton.dart' as Skeleton;

main () {
  useHtmlEnhancedConfiguration();

  Skeleton.run();
}
The only difference here is the use of the html_enhanced_config.dart library to draw pretty test results in the browser.

As for the skeleton.dart test file, I do the usual thing of importing the unittest library, importing the skeleton file itself and then defining my expectation:
library skeleton_snippet_test;

import 'package:unittest/unittest.dart';

import '../public/scripts/skel.dart' as Skeleton;

run() {
  group("[skeleton]", (){
    test('the skeleton app returns OK', (){
      expect(Skeleton.main, returnsNormally);
    });
  });
}
I really appreciate the rich variety of test matchers that come built into the Dart unit testing library. The returnsNormally matcher will pass as long as the supplied function returns without exception or error.

When I finally load this test up in Dartium, however, I hit a compile-time error:
Internal error: 'file:///Code/your_first_dart_app/public/scripts/skel.dart': Error: line 1 pos 1: library name definition expected
import('dart:html');
^ 
I cannot import something that is not a library. Unfortunately, I cannot include external source unless it is declared an exclusive part of another library either, so I am stuck adding a library declaration to the top of the code being tested:
library skeleton_example;

import 'dart:html';
import 'dart:json';
main() {
  loadComics();
}
loadComics() {
  // Do stuff here
  throw new UnsupportedError();
}
That is less than ideal since I am testing a standalone script, not a library. Also, this is way too early in the book to be introducing libraries—the only reason that I use them here is for testing the code that will be included in the book.

Unfortunately, I cannot think of another way to be able to test these standalone snippets. The library statement does not change in the behavior of the real application. So I will likely strip the library statement from the book, but leave it in the actual code to keep it testable.

This is not a Dart deficiency. I am writing my application code contrary to Dart conventions so I have to live with the consequences. Including a library statement is a small consequence.

With the library statement in place, I load up my test page again and find that I have a passing test:


Solely for my own edification, I alter the code to see what happens if the main() function would not return normally:
main() {
  loadComics();
}
loadComics() {
  // Do stuff here
  throw new UnsupportedError();
}
Now, I get a pretty old backtrace:


OK, perhaps the backtrace is not pretty, but the error message sure is: "Expected: return normally but: threw <No such method: 'UnsupportedError'." That is thanks to the returnsMatcher that I used. I love stuff like that. I am really enjoying testing with Dart.


Day #608

No comments:

Post a Comment