|
| 1 | +# Testing: `test/` |
| 2 | + |
| 3 | +Lab's JavaScript tests use [Vows](http://vowsjs.org), an asynchronous behavior driven framework based |
| 4 | +on [Node.js](http://nodejs.org/). In addition Lab uses [jsdom](https://github.com/tmpvar/jsdom), a |
| 5 | +lightweight CommonJS implementation of the W3C DOM specifications. Lab's test setup was inspired |
| 6 | +by that used by [d3.js](http://mbostock.github.com/d3/). The development dependencies for running the |
| 7 | +tests are installed using [npm](http://npmjs.org/). |
| 8 | + |
| 9 | +Running the tests: |
| 10 | + |
| 11 | + $ make test |
| 12 | + ................................. . . .. . . . |
| 13 | + x OK > 40 honored (0.012s) |
| 14 | + |
| 15 | +If you are running `bin/guard` the tests run automatically anytime a change is made in the JavaScript |
| 16 | +files in the `src/` or `test/` directory. |
| 17 | + |
| 18 | +The results of the tests are displayed in the console that `bin/guard` is running in. |
| 19 | + |
| 20 | +If the bottom of the console window is viewable you will see new test results whenever you save a changes. |
| 21 | + |
| 22 | +Recent versions of nodejs/v8 support TypedArrays -- this make it possible to more extensively |
| 23 | +test lab.arrays which is designed to support using either typed or regular arrays for computation. |
| 24 | + |
| 25 | +`test/env.js` uses the node module [jsdom](https://github.com/tmpvar/jsdom) to setup resources for |
| 26 | +simple emulation of a browser. |
| 27 | + |
| 28 | +[Vows](http://vowsjs.org) integrates the [standard nodejs assertions](http://nodejs.org/docs/latest/api/assert.html) |
| 29 | +with an additional collection of useful [assertions](http://vowsjs.org/#assertions) summarized below: |
| 30 | + |
| 31 | +- numerical |
| 32 | + |
| 33 | + assert.greater (3, 2); |
| 34 | + assert.lesser (2, 3); |
| 35 | + assert.inDelta (Math.random(), 0, 1); |
| 36 | + |
| 37 | +- equality |
| 38 | + |
| 39 | + assert.equal (4, 4); |
| 40 | + assert.strictEqual (4 > 2, true); |
| 41 | + assert.notEqual (4, 2); |
| 42 | + assert.strictNotEqual (1, true); |
| 43 | + assert.deepEqual ([4, 2], [4, 2]); |
| 44 | + assert.notDeepEqual ([4, 2], [2, 4]); |
| 45 | + |
| 46 | +- type |
| 47 | + |
| 48 | + assert.isFunction (function () {}); |
| 49 | + assert.isObject ({goo:true}); |
| 50 | + assert.isString ('goo'); |
| 51 | + assert.isArray ([4, 2]); |
| 52 | + assert.isNumber (42); |
| 53 | + assert.isBoolean (true); |
| 54 | + assert.typeOf (42, 'number'); |
| 55 | + assert.instanceOf ([], Array); |
| 56 | + |
| 57 | +- truth |
| 58 | + |
| 59 | + assert.isTrue (true); |
| 60 | + assert.isFalse (false); |
| 61 | + |
| 62 | +- null, undefined, NaN |
| 63 | + |
| 64 | + assert.isNull (null); |
| 65 | + assert.isNotNull (undefined); |
| 66 | + assert.isUndefined ('goo'[9]); |
| 67 | + assert.isNaN (0/0); |
| 68 | + |
| 69 | +- inclusion |
| 70 | + |
| 71 | + assert.include ([4, 2, 0], 2); |
| 72 | + assert.include ({goo:true}, 'goo'); |
| 73 | + assert.include ('goo', 'o'); |
| 74 | + |
| 75 | +- regexp matching |
| 76 | + |
| 77 | + assert.match ('hello', /^[a-z]+/); |
| 78 | + |
| 79 | +- length |
| 80 | + |
| 81 | + assert.length ([4, 2, 0], 3); |
| 82 | + assert.length ('goo', 3); *** not working *** |
| 83 | + |
| 84 | +- emptiness |
| 85 | + |
| 86 | + assert.isEmpty ([]); |
| 87 | + assert.isEmpty ({}); |
| 88 | + assert.isEmpty (""); |
| 89 | + |
| 90 | +- exceptions |
| 91 | + |
| 92 | + assert.throws(function () { x + x }, ReferenceError); |
| 93 | + assert.doesNotThrow(function () { 1 + 1 }, Error); |
| 94 | + |
| 95 | +Additionally `test/env-assert.js` has a number of useful additional assertions copied from |
| 96 | +[d3.js](http://mbostock.github.com/d3/). |
| 97 | + |
| 98 | +_**Note**: Using a more specific assertion usually results in more useful error reports._ |
| 99 | + |
| 100 | +There are also many interesting test examples and patterns in the |
| 101 | +[d3.js test directory](https://github.com/mbostock/d3/tree/master/test) that can be adapted for use in Lab. |
| 102 | + |
| 103 | +## A Simple Example of Test Driven Development |
| 104 | + |
| 105 | +Here's a simple example that is part of the tests for `lab.arrays.js` to test the `arrays.max()` function: |
| 106 | + |
| 107 | + "find max in array with negative and positive numbers": function(max) { |
| 108 | + assert.equal(max([3, -1, 0, 1, 2, 3]), 3); |
| 109 | + }, |
| 110 | + |
| 111 | +The 'model stepping' tests are a good example where the tests help helped drive new features. The basic |
| 112 | +features I was testing in this section relate to the existing functionality exposed by the Stop, Start, Go, and |
| 113 | +Reset buttons as wells as the extended keyboard controls that allow stepping forward and backwards a step at a time. |
| 114 | + |
| 115 | +First I created this test that passed: |
| 116 | + |
| 117 | + "after running running one tick the model is at step 1": function(model) { |
| 118 | + model.tick(); |
| 119 | + assert.equal(model.stepCounter(), 1); |
| 120 | + assert.isTrue(model.isNewStep()); |
| 121 | + }, |
| 122 | + |
| 123 | +In thinking about driving out changes to KE, PE and Temperature of the molecular model itself I realized |
| 124 | +I'd like the capability to run a specific number of steps forward and then check the results. |
| 125 | + |
| 126 | +I then wrote this test that failed -- because the model.tick() function didn't yet take an optional argument to |
| 127 | +run multiple steps forward: |
| 128 | + |
| 129 | + "after running 9 more ticks the model is at step 10": function(model) { |
| 130 | + model.tick(9); |
| 131 | + assert.equal(model.stepCounter(), 10); |
| 132 | + assert.isTrue(model.isNewStep()); |
| 133 | + }, |
| 134 | + |
| 135 | +After saving the change I saw the new test failure reported in my console. I then implemented the new |
| 136 | +feature in the actual `src/lab/molecules.js`. Less than a second after saving the file the tests |
| 137 | +completed and the report showed it passing. |
| 138 | + |
| 139 | +This is a very simple example -- but part of the value of this kind of test driven development is in first |
| 140 | +thinking of how something should behave rather than in how to get it to actually do the work. |
| 141 | + |
| 142 | +Since I already had this function for running one model step: |
| 143 | + |
| 144 | + model.tick() |
| 145 | + |
| 146 | +Adding an optional numeric argument for running more steps is a fine way to express the intent of the new feature: |
| 147 | + |
| 148 | + model.tick(9) |
| 149 | + |
| 150 | +In more complicated coding thinking about how to express the intent clearly and then what the result |
| 151 | +should be if that intent is successful **FIRST** ... and then 'driving out' the actual implementation to |
| 152 | +achieve that result can result in a better architecture -- and of course you also end up with tests. |
| 153 | + |
| 154 | +Because the tests run SO quickly I can interactively change the code in the module or the test and |
| 155 | +immediately see results. |
| 156 | + |
| 157 | +## Debugging Tests using the node debugger |
| 158 | + |
| 159 | +Sometimes it can be helpful to break into a debugger when there is a problem in either the code |
| 160 | +or the test setup itself. Node comes with a [debugger](http://nodejs.org/docs/latest/api/debugger.html) |
| 161 | +which can be used in combination with vows and the tests. |
| 162 | + |
| 163 | +First set a breakpoint by inserting the statement: `debugger;` |
| 164 | + |
| 165 | + suite.addBatch({ |
| 166 | + "Thermometer": { |
| 167 | + topic: function() { |
| 168 | + debugger; |
| 169 | + return new components.Thermometer("#thermometer"); |
| 170 | + }, |
| 171 | + "creates thermometer": function(t) { |
| 172 | + assert.equal(t.max, 0.7) |
| 173 | + } |
| 174 | + } |
| 175 | + }); |
| 176 | + |
| 177 | +Start the node debugger and pass in the full command line to run the tests: |
| 178 | + |
| 179 | + node debug ./node_modules/vows/bin/vows --no-color |
| 180 | + |
| 181 | +The debugger will break at the beginning of vows: |
| 182 | + |
| 183 | + < debugger listening on port 5858 |
| 184 | + connecting... ok |
| 185 | + break in node_modules/vows/bin/vows:3 |
| 186 | + 1 |
| 187 | + 2 |
| 188 | + 3 var path = require('path'), |
| 189 | + 4 fs = require('fs'), |
| 190 | + 5 util = require('util'), |
| 191 | + |
| 192 | +Enter `cont` to continue execution until your breakpoint. |
| 193 | + |
| 194 | + debug> cont |
| 195 | + < · |
| 196 | + < ········ |
| 197 | + < · |
| 198 | + < |
| 199 | + break in test/lab/components/components-test.js:13 |
| 200 | + 11 "Thermometer": { |
| 201 | + 12 topic: function() { |
| 202 | + 13 debugger; |
| 203 | + 14 return new components.Thermometer("#thermometer"); |
| 204 | + 15 }, |
| 205 | + |
| 206 | +To evaluate expressions type `repl` -- use ctrl-C to exit the repl: |
| 207 | + |
| 208 | + repl |
| 209 | + Press Ctrl + C to leave debug repl |
| 210 | + > initialization_options |
| 211 | + { model_listener: false, |
| 212 | + lennard_jones_forces: true, |
| 213 | + coulomb_forces: true } |
| 214 | + > atoms[0].charge |
| 215 | + -1 |
| 216 | + |
| 217 | +Enter **ctrl-C** to exit the repl and return to the debugger. |
| 218 | + |
| 219 | +Enter **ctrl-D** to exit the debugger. |
| 220 | + |
| 221 | +[node-inspector](https://github.com/dannycoates/node-inspector) |
| 222 | +[npm package for node-inspector](http://search.npmjs.org/#/node-inspector) |
| 223 | + |
| 224 | +### Using node-inspector while debugging |
| 225 | + |
| 226 | +[node-inspector](https://npmjs.org/package/node-inspector) supports using the webkit inspector in Chrome to support |
| 227 | +interactive debugging in node. This can be particualrly help when debugging tests. |
| 228 | + |
| 229 | +Example: |
| 230 | + |
| 231 | +Add a debugger statement to your test: |
| 232 | + |
| 233 | + originalModelJson = fs.readFileSync(testDir + "expected-json/" + modelJsonFile).toString(); |
| 234 | + modelName = /\/?([^\/]*)\.json/.exec(modelJsonFile)[1]; |
| 235 | + debugger; |
| 236 | + originalModel = JSON.parse(originalModelJson); |
| 237 | + model = new Model(originalModel); |
| 238 | + |
| 239 | +Start a debugging session: |
| 240 | + |
| 241 | + $ node --debug-brk ./node_modules/vows/bin/vows --no-color test/vows/mml-conversions/deserialize-serialize-test.js |
| 242 | + debugger listening on port 5858 |
| 243 | + |
| 244 | +Start node-inspector: |
| 245 | + |
| 246 | + $ ./node_modules/.bin/node-inspector & |
| 247 | + info - socket.io started |
| 248 | + visit http://0.0.0.0:8080/debug?port=5858 to start debugging |
0 commit comments