1 | # zora
|
2 |
|
3 | Fast javascript test runner for **nodejs** and **browsers**
|
4 |
|
5 | [![CircleCI](https://circleci.com/gh/lorenzofox3/zora.svg?style=svg)](https://circleci.com/gh/lorenzofox3/zora)
|
6 |
|
7 | ## installation
|
8 | ``npm install --save-dev zora``
|
9 |
|
10 | ## (Un)Opinions and Design
|
11 |
|
12 | These are the following rules and ideas I have followed while developing zora. Whether they are right or not is an entire different topic ! :D
|
13 | Note I have decided to develop zora specially because I was not able to find a tool which complies entirely with these ideas.
|
14 |
|
15 | ### Tests are regular Javascript programs.
|
16 |
|
17 | You don't need a specific test runner, a specific platform or any build step to run your `zora` tests. They are only regular valid EcmaScript 2017 programs.
|
18 | If you have the following test.
|
19 | ```Javascript
|
20 | import test from 'path/to/zora';
|
21 |
|
22 | test('should result to the answer', t=>{
|
23 | const answer = 42
|
24 | t.equal(answer, 42, 'answer should be 42');
|
25 | });
|
26 | ```
|
27 |
|
28 | You can run your test with
|
29 | 1. Node: ``node ./myTestFile.js``
|
30 | 2. In the browser ``<script type="module" src="./myTestFile.js></script>`` identically
|
31 |
|
32 | Moreover zora does not use specific platform API which should make it transparent to most of your tools such module bundlers or transpilers.
|
33 |
|
34 | In few words:
|
35 | > Zora is only Javascript, no less, no more.
|
36 |
|
37 | ### Tests are fast
|
38 |
|
39 | Tests are part of our daily routine as software developers. Performance is part of the user experience and there is no reason you should wait seconds for your tests to run.
|
40 | Zora is by far the **fastest** Javascript test runner in the ecosystem.
|
41 |
|
42 | ### Benchmark
|
43 |
|
44 | This repository includes a benchmark which consists on running N test files, with M tests in each and where one tests lasts T milliseconds.
|
45 | About 10% of tests should fail.
|
46 |
|
47 | 1. profile library: N = 5, M = 8, T = 25ms
|
48 | 2. profile web app: N = 10, M = 8, T = 40ms
|
49 | 3. profile api: N =12, M = 10, T = 100ms
|
50 |
|
51 | Each framework runs with its default settings
|
52 |
|
53 | Here are the result of different test frameworks on my developer machine with node 9.3.0 :
|
54 |
|
55 | | | zora@2.0.0 | tape@4.8.0 | Jest@22.2.2 | AvA@0.25.0 | Mocha@5.0.0|
|
56 | |--------|:------------:|:-----------:|:-------------:|:------------:|------------:|
|
57 | |Library | 118ms | 1239ms | 1872ms | 1703ms | 1351ms |
|
58 | |Web app | 142ms | 3597ms | 4356ms | 2441ms | 3757ms |
|
59 | |API | 203ms | 12637ms | 13385ms | 2966ms | 12751ms |
|
60 |
|
61 | Of course as any benchmark, it may not cover your use case and you should probably run your own tests before you draw any conclusion
|
62 |
|
63 | ### Focus on tests only
|
64 |
|
65 | zora does one thing but hopefully does it well: **test**.
|
66 |
|
67 | In my opinions:
|
68 | 1. Pretty reporting (I have not said *efficient reporting*) should be handled by a specific tool.
|
69 | 2. Transipilation and other code transformation should be handled by a specific tool.
|
70 | 3. File watching and caching should be handled by a specific tool.
|
71 | 4. File serving should be handled by a specific tool.
|
72 | 5. Coffee should me made by a specific tool.
|
73 |
|
74 | As a result zora is only **1Kb** of code whereas it lets you use whatever better tool you want for any other specific task you may need within your workflow.
|
75 |
|
76 | ### Control flow is managed the way you want with regular Javascript idioms
|
77 |
|
78 | Lately Ecmascript specification has improved a lot (and its various implementation) in term of asynchronous code.
|
79 | There is no reason you could not benefit from it while writing your tests.
|
80 | In zora, asynchronous code is as simple as async function, you do not need to manage a plan or to notify the test framework the end of a test
|
81 |
|
82 | ```Javascript
|
83 | import test from 'zora';
|
84 |
|
85 | test('should handle async operation', async t => {
|
86 | const user = await getUserAsync();
|
87 | t.deepEqual(user,{name:'John doe'});
|
88 | });
|
89 | ```
|
90 |
|
91 | ### Reporter is handled with other process (TAP)
|
92 |
|
93 | When you run a test you usually want to know whether there is any failure, where and why in order to debug and solve the issue as fast as possible.
|
94 | Whether you want it to be printed in red, yellow etc is a matter of preference.
|
95 |
|
96 | For this reason, zora output [TAP](http://testanything.org/) (Test Anything Protocol) by default. This protocol is widely used and [there are plenty of tools](https://github.com/sindresorhus/awesome-tap) to parse and deal with it the way **you** want.
|
97 |
|
98 | You are not stuck into a specific reporting format, you'll be using a *standard* instead.
|
99 |
|
100 | ## Usage
|
101 |
|
102 | Zora API is very straightforward: you have only **1** function and the assertion library is pretty standard and obvious.
|
103 | The tests will start by themselves with no extra effort.
|
104 |
|
105 | ```Javascript
|
106 | import test from 'zora';
|
107 |
|
108 | test('some independent test', t=>{
|
109 | t.equal(1+1,2,'one plus one should equal 2');
|
110 | });
|
111 |
|
112 | test('other independent test', t=>{
|
113 | t.equal(1+2,3,'one plus two should equal 3');
|
114 | });
|
115 | ```
|
116 |
|
117 | ### Assertions API
|
118 |
|
119 | The assertion api you can use within your test is pretty simple and highly inspired by [tape](https://github.com/substack/tape)
|
120 | * ok
|
121 | * notOk
|
122 | * equal
|
123 | * notEqual
|
124 | * deepEqual
|
125 | * notDeepEqual
|
126 | * throws
|
127 | * doesNotThrow
|
128 | * fail
|
129 |
|
130 | ### Control flow
|
131 |
|
132 | Notice that each test runs in its own micro task in parallel (for performance). It implies your tests should not depend on each other.
|
133 | It is often a good practice.
|
134 | However, you'll be able to group your tests if you wish to conserve some state between them or wait one to finish before you start another one (ideal with tests running against real database).
|
135 |
|
136 | The sequence is simply controlled by AsyncFunction (and await keyboard)
|
137 |
|
138 | ```Javascript
|
139 |
|
140 | let state = 0;
|
141 |
|
142 | test('test 1', t=>{
|
143 | t.ok(true);
|
144 | state++;
|
145 | });
|
146 |
|
147 | test('test 2', t=>{
|
148 | //Maybe yes maybe no, you have no guarantee ! In this case it will work as everything is sync
|
149 | t.equal(state, 1);
|
150 | });
|
151 |
|
152 | //Same thing here even in nested tests
|
153 | t.test('grouped', t=>{
|
154 | let state = 0;
|
155 |
|
156 | t.test('test 1', t=>{
|
157 | t.ok(true);
|
158 | state++;
|
159 | });
|
160 |
|
161 | t.test('test 2', t=>{
|
162 | //Maybe yes maybe no, you have no guarantee ! In this case it will work as everything is sync
|
163 | t.equal(state, 1);
|
164 | });
|
165 | });
|
166 |
|
167 | //And
|
168 | t.test('grouped', t=>{
|
169 | let state = 0;
|
170 |
|
171 | t.test('test 1', async t=>{
|
172 | t.ok(true);
|
173 | await wait(100);
|
174 | state++;
|
175 | });
|
176 |
|
177 | test('test 2', t=>{
|
178 | t.equal(state, 0, 'see the old state value as it will have started to run before test 1 is done');
|
179 | });
|
180 | });
|
181 |
|
182 | //But
|
183 | t.test('grouped', async t=>{
|
184 | let state = 0;
|
185 |
|
186 | //specifically wait the end of this test before continuing !
|
187 | await t.test('test 1', async t=>{
|
188 | t.ok(true);
|
189 | await wait(100);
|
190 | state++;
|
191 | });
|
192 |
|
193 | test('test 2', t=>{
|
194 | t.equal(state, 1, 'see the updated value!');
|
195 | });
|
196 | });
|
197 | ```
|
198 |
|
199 | ### BDD style
|
200 |
|
201 | zora also allows you to nest tests in each other in a [BDD style](https://en.wikipedia.org/wiki/Behavior-driven_development).
|
202 | You just need to call the `test` property of zora at root level.
|
203 |
|
204 | ```
|
205 | import zora from 'zora';
|
206 | const describe = zora.test;
|
207 |
|
208 | // and write you tests
|
209 | describe('test 1', t => {
|
210 |
|
211 | t.ok(true, 'assert1');
|
212 |
|
213 | // inside simply call t.test for nested test
|
214 | t.test('some nested test', t => {
|
215 | t.ok(true, 'nested 1');
|
216 | t.ok(true, 'nested 2');
|
217 | });
|
218 |
|
219 | t.test('some nested test bis', t => {
|
220 | t.ok(true, 'nested 1');
|
221 |
|
222 | t.test('deeply nested', t => {
|
223 | t.ok(true, 'deeply nested really');
|
224 | t.ok(true, 'deeply nested again');
|
225 | });
|
226 |
|
227 | t.ok(true, 'nested 2');
|
228 | });
|
229 |
|
230 | t.ok(true, 'assert2');
|
231 | });
|
232 |
|
233 | describe('test 2', t => {
|
234 | t.ok(true, 'assert3');
|
235 |
|
236 | t.test('nested in two', t => {
|
237 | t.ok(true, 'still happy');
|
238 | });
|
239 |
|
240 | t.ok(true, 'assert4');
|
241 | });
|
242 | ```
|
243 |
|
244 | The output is a valid tap output where sub plans are indented
|
245 | ```
|
246 | TAP version 13
|
247 | # Subtest: test 1
|
248 | ok 1 - assert1
|
249 | # Subtest: some nested test
|
250 | ok 1 - nested 1
|
251 | ok 2 - nested 2
|
252 | 1..2
|
253 | # time=1ms
|
254 | ok 2 - some nested test # time=1ms
|
255 | # Subtest: some nested test bis
|
256 | ok 1 - nested 1
|
257 | # Subtest: deeply nested
|
258 | ok 1 - deeply nested really
|
259 | ok 2 - deeply nested again
|
260 | 1..2
|
261 | # time=1ms
|
262 | ok 2 - deeply nested # time=1ms
|
263 | ok 3 - nested 2
|
264 | 1..3
|
265 | # time=1ms
|
266 | ok 3 - some nested test bis # time=1ms
|
267 | ok 4 - assert2
|
268 | 1..4
|
269 | # time=1ms
|
270 | ok 1 - test 1 # time=1ms
|
271 | # Subtest: test 2
|
272 | ok 1 - assert3
|
273 | # Subtest: nested in two
|
274 | ok 1 - still happy
|
275 | 1..1
|
276 | # time=1ms
|
277 | ok 2 - nested in two # time=1ms
|
278 | ok 3 - assert4
|
279 | 1..3
|
280 | # time=1ms
|
281 | ok 2 - test 2 # time=1ms
|
282 | 1..2
|
283 | # ok
|
284 | ```
|
285 |
|
286 | The structure can be parsed with common tap parser (such as [tap-parser]()) And will be parsed as well by tap parser which
|
287 | do not understand the indentation. However to take full advantage of the structure you should probably use a formatter (such [tap-mocha-reporter](https://www.npmjs.com/package/tap-mocha-reporter)) aware of this specific structure to get the whole benefit
|
288 | of the format.
|
289 |
|
290 | ![tap output in a BDD format](./media/bsd.png)
|
291 |
|
292 | ### In the browser
|
293 |
|
294 | Zora itself does not depend on native nodejs modules (such file system, processes, etc) so the code you will get is regular EcmaScript.
|
295 |
|
296 | // todo add some more recipe (WIP) karma, dependencies, transpilations etc
|
297 |
|
298 | #### drop in file
|
299 | You can simply drop the dist file in the browser and write your script below (or load it).
|
300 | You can for example play with this [codepen](http://codepen.io/lorenzofox3/pen/zoejxv?editors=1112)
|
301 |
|
302 | ```Html
|
303 | <!-- some content -->
|
304 | <body>
|
305 | <script type="module">
|
306 |
|
307 | import test from 'path/to/zora';
|
308 |
|
309 | test('some test', (assert) => {
|
310 | assert.ok(true, 'hey there');
|
311 | })
|
312 |
|
313 | test('some failing test', (assert) => {
|
314 | assert.fail('it failed');
|
315 | })
|
316 | </script>
|
317 | </body>
|
318 | <!-- some content -->
|
319 | ```
|
320 |
|
321 | #### As part of CI (example with rollup)
|
322 | I will use [rollup](http://rollupjs.org/) for this example, but you should not have any problem with [webpack](https://webpack.github.io/) or [browserify](http://browserify.org/). The idea is simply to create a test file your testing browsers will be able to run.
|
323 |
|
324 | assuming you have your entry point as follow :
|
325 | ```Javascript
|
326 | //./test/index.js
|
327 | import test1 from './test1.js'; // some tests here
|
328 | import test2 from './test2.js'; // some more tests there
|
329 | import test3 from './test3.js'; // another test plan
|
330 | ```
|
331 |
|
332 | where for example ./test/test1.js is
|
333 | ```Javascript
|
334 | import test from 'zora';
|
335 |
|
336 | test('mytest',(assertions) => {
|
337 | assertions.ok(true);
|
338 | })
|
339 |
|
340 | test('mytest',(assertions) => {
|
341 | assertions.ok(true);
|
342 | });
|
343 | ```
|
344 | you can then bundle your test as single app.
|
345 |
|
346 | ```Javascript
|
347 | const node = require('rollup-plugin-node-resolve');
|
348 | const commonjs = require('rollup-plugin-commonjs');
|
349 | module.exports = {
|
350 | input: './test/index.js',
|
351 | output:[{
|
352 | name:'test',
|
353 | format:'test',
|
354 | sourcemap:'inline' // ideal to debug
|
355 | }]
|
356 | plugins: [node(), commonjs()], //you can add babel plugin if you need transpilation
|
357 | };
|
358 | ```
|
359 |
|
360 | You can now drop the result into a debug file
|
361 | ``rollup -c path/to/conf > debug.js``
|
362 |
|
363 | And read with your browser (from an html document for example).
|
364 |
|
365 | ![tap output in the browser console](./media/console-sc.png)
|
366 |
|
367 | Even better, you can use tap reporter browser friendly such [tape-run](https://www.npmjs.com/package/tape-run) so you'll have a proper exit code depending on the result of your tests.
|
368 |
|
369 | so all together, in your package.json you can have something like that
|
370 | ```Javascript
|
371 | {
|
372 | // ...
|
373 | "scripts": {
|
374 | "test": "rollup -c path/to/conf | tape-run"
|
375 | }
|
376 | // ...
|
377 | }
|
378 | ``` |
\ | No newline at end of file |