Friday, February 24, 2012

Checked Dart Testing

‹prev | My Chain | next›

An intriguing feature of Dart is the ability to run the VM in checked mode. As I have coded Dart for the past couple of months, I have noticed a certain... looseness in its enforcement of types. It is not just that I avoided declaring any type information for the better part of the first month that I coded in Dart. I have also noticed that Dart is perfectly willing to let me declare a variable as one thing, but assign something else entirely to it.

I have come to regard types in Dart as little more than documentation. To be sure, it is very useful documentation, but since types are not enforced (as best I can tell), I can code very like I am used to in dynamic languages like Ruby and Javascript.

There are times, though, that it bothers me knowing that I could be missing out on bugs that loose typing might be allowing. That was part of the motivation of getting comfortable with testing these last couple of days, but still... I should not be able to assign a list of Maps:
  test('HipsterCollection has multiple models', (){
    HipsterCollection it = new HipsterCollection();
    it.models = [{'id': 17}, {'id': 42}];

    Expect.equals(2, it.length);
  });
Because I declared models as a list of HipsterModels:
class HipsterCollection implements Collection {
  List<HipsterModel> models;
//...
Or maybe I should. I am not wise in the way of typing so maybe that is perfectly OK. There is an easy way to find out—run my test suite in Dart's checked mode.

The current state of my test suite for the HipsterCollection in my Hipster MVC is all green:


As I said, I am reasonably sure that some of those should not be passing because of mismatched types. So I start up a recently downloaded copy of Dartium with the checked flags:
DART_FLAGS='--enable_type_checks --enable_asserts' dartium
When I reload and rerun my test suite, I do have a failure, but not exactly where I expected:


Running test:HipsterCollection add dispatch add event
  Result: ERROR Caught 'bootstrap_impl': Failed type check: line 1193 pos 14: type 'LinkedHashMapImplementation<Dynamic, Dynamic>' is not assignable to type 'HipsterModel' of 'value'.
  took 10 ms
Breakpoints do not seem to be working in the Dartium build that I have (r4584 based on the directory name in the download), so I resort to good old print-STDERR debugging:
Running test:HipsterCollection add dispatch add event
[asyncTest] HipsterCollection add
[add] list add
  Result: ERROR Caught 'bootstrap_impl': Failed type check: line 1193 pos 14: type 'LinkedHashMapImplementation<Dynamic, Dynamic>' is not assignable to type 'HipsterModel' of 'value'.
  took 11 ms
The last statement output comes from inside HipsterCollection:
class HipsterCollection {
  // ...
  add(model) {
    print("[add] list add");
    models.add(model);
    print("[add] add listern");
    on.add.
      dispatch(new CollectionEvent('add', this, model:model));
  }
  // ...
}
Interesting. It seems that I can overwrite the List<HipsterModel> models instance variable with a List<Map>:
  test('HipsterCollection has multiple models', (){
    HipsterCollection it = new HipsterCollection();
    it.models = [{'id': 17}, {'id': 42}];

    Expect.equals(2, it.length);
  });
But I cannot add a Map to List<HipsterModel> models instance variable. I do not quite understand this, but I do know how to resolve the failure. I need to import HispterModel into the test suite so that I can create a dummy sub-class:
#import("../public/scripts/HipsterModel.dart");

class TestHipsterModel extends HipsterModel {
  TestHipsterModel() : super({});
}
Now, I can add an instance of a HipsterModel to the collection:
 asyncTest('HipsterCollection add dispatch add event', 1, () {
    noOpSync(method, model, [options]) {}
    HipsterSync.sync = noOpSync;

    HipsterCollection it = new TestHipsterCollection();

    it.
      on.
      add.
      add((event) {
        callbackDone();
      });

    it.add(new TestHipsterModel());
  });
And I have my test suite passing again:


I don't know that I have really gained much through this exercise of typed checking testing. If anything, I was closer to using mocks in a situation in which it was fine to do so. I think tomorrow I will explore more of the code to see if there are any other potential benefits to this type checking stuff.


Day #306

2 comments:

  1. You are completely right that it would be strange if a List<Map> could be assigned to a List<HipsterModel>. In your example you are not assigning a List<Map> to a list of List<HipsterModel> but a List<Dynamic> to a List<HipsterModel> and that is allowed. Basically Dynamic stands for "trust me. it's right".
    Now, why is it a List<Dynamic> and not a List<Map>? A list literal without type-annotation defaults to being a List<Dynamic>. The best example why this is necessary is: var x = [];
    Without any elements the array needs to be created as List<Dynamic> so that any element can then be added to it. This remains true if you initialize your list with elements.
    Dart cannot check dynamically if the elements are right either: Map is an interface, so there could be *any* concrete class behind it. Just accessing the elements (to verify their type) could do all kinds of things (and would be horribly slow).

    Now there is another question concerning const literals: var x = const [1, 2];
    Even in this case Dart cannot infer a type. Well, actually in this one it could, but in general it can not:
    var x = const [3, "foo"];
    The least upper bound would be type "Object". However the user might want to call "hashCode" on each element and use 'x' as a List<Hashable>.

    But there is a way to give literals a type:
    var x = <Hashable>[3, "foo"];
    or x = const <Hashable>[3, "foo"];

    ReplyDelete
    Replies
    1. What surprises me is that assigning a List<Dynamic> to a List<HipsterModel> is allowed. If I declare a variable as one type, how is it possible to assign another type to it?

      More specifically, shouldn't `List<HipsterModel> models` constrain models to being assigned to a list of objects with upper bound HipsterModel?

      Is the stuff inside angle brackets just window dressing when declaring variables?

      Delete