Saturday, July 10, 2010

A Problem with Names

‹prev | My Chain | next›

I finished up with vows.js testing of the fab.js libraries in my (fab) game. I finished, but I feel as though the tests are trying very hard to tell me that I need to tidy up a thing or two.

First up, I think the name of my last (fab) app is questionable: unary_try. Two uses of this function are:
  ( /move/ )
( unary_try( function () {
update_player_status(JSON.parse(""+this));
broadcast(comet_walk_player(this));
} ) )

( /chat/ )
( unary_try( function () {
var msg = JSON.parse(this.toString());
msg.body = msg.say.substr(0,100);
broadcast(comet_player_say(JSON.stringify(msg)));
} ) )
The "unary" part reflects that it generates a unary, or upstream (fab) app. The "try" part of the name indicates that the function passed in gets called if there is a body POSTed by the browser. The "try" is also because there is a try-catch-finally block around the supplied function.

Ugh. Neither of those really help me figure out what this function does when I use it. The name describes what it is more than what it does. Specifically, it invokes the supplied function if there is a POSTed body. Maybe call_if_body? Better yet, if_body:
  ( /move/ )
( if_body(
function () {
update_player_status(JSON.parse(""+this));
broadcast(comet_walk_player(this));
} ) )

( /chat/ )
( if_body(
function () {
var msg = JSON.parse(this.toString());
msg.body = msg.say.substr(0,100);
broadcast(comet_player_say(JSON.stringify(msg)));
} ) )
Hmmm... I kinda like that. It is an app that invokes the supplied function if there is a POSTed body. I let the convention of fab indentation indicate that this produces a unary app. The "try" is now incidental part of the implementation.

The next thing that is bothering me is the topic of my vows.js test for this app. I have three different topics in my spec... for six tests. That would not be so bad if the three different topics were significantly different:
    callback_when_send_obj: function(obj) {
return function () {
var topic = this;
var upstream_listener = if_body(
function(obj) { topic.callback(null, obj); }
).call(function() {});

upstream_listener(obj);
};
},
downstream_when_exception: function() {
return function () {
var topic = this;
var upstream_listener = if_body(
function() { throw "Foo!"; }
).call(
function(obj) { topic.callback(null, obj); }
);

upstream_listener({body:"foo"});
};
},
downstream_when_send_obj: function(obj) {
return function () {
var topic = this;
var upstream_listener = if_body(
function() {}
).call(
function(obj) { topic.callback(null, obj); }
);

upstream_listener(obj);
};
}
The three different topic generators reflect wanting to test the function supplied to if_body and the response from if_body given various inputs (including a thrown exception). Normally, I do not care about DRY in test code, but those are not tests—just API calls. Besides, I am not fond of any of the names of those API calls. Maybe DRYing them up will suggest better naming and usage.

I start with the last two API calls that are used for testing downstream responses. The vows.js callback goes in the same place for both—where the downstream app would go in a real fab.js application. I can combine them into a single function that takes the data being POSTed to the resource as the first argument and an optional second argument that is the callback invoked if_body:
function downstream_response(obj, fn) {
if (!fn) fn = function() {};
return function () {
var topic = this;
var upstream_listener = if_body(fn).
call(function(obj) { topic.callback(null, obj); });

upstream_listener(obj);
};
};
I have renamed the helper function as downstream_response, which ought to read much better as a vows.js topic. If the if_body callback is not supplied, then an empty, anonymous function is used. The callback does not affect the response, its only purpose is to change local state.

I also rename the other helper function as args_to_callback. With those changes, I have vows that read like:
    'callback, with a body': {
topic: args_to_callback({body: "foo"}),
'invokes callback with body': function(obj) {
assert.equal(obj, "foo");
}
},
'downstream, with a body': {
topic: downstream_response({body: "foo"}),
'terminates connection': function(obj) {
assert.isUndefined(obj);
}
},
//...
'downstream, with exception': {
topic: downstream_response({body: "foo"}, function(){throw "foo";}),
'terminates connection': function(obj) {
assert.isUndefined(obj);
}
}
I am much happier with that. For each vow, I can readily discern the topic of the test: the args sent to the callback with a POSTed body of "foo", the response downstream with the same POST, and the response when an exception occurs in the callback.

And all of my tests still pass:
cstrom@whitefall:~/repos/my_fab_game$ vows --spec test/if_body_test.js 

♢ if_body

callback, with a body
✓ invokes callback with body
downstream, with a body
✓ terminates connection
downstream, with HTTP headers
✓ terminates connection
downstream, with empty request
✓ terminates connection
downstream, with exception
✓ terminates connection

✓ OK » 5 honored (0.015s)
That is a nice stopping point for today. Up tomorrow: poke my head back up to make sure that everything is still working in the game itself. If so, then I will likely look into using vows.js to test some of the state-based portion of the game.


Day #160

No comments:

Post a Comment