# test.js Combinational test framework. This is not meant as a replacement for more advanced and feature-rich testing frameworks, instead this is a minimalist and complete experimental implementation of a specific approach to testing, i.e. _combinational testing_ Note, **this module is experimental** and can change quite allot within a shot to mid timeframe, use at your own risk, though ideas, feedback and suggestions are welcome. ## Features - Simple / minimalist implementation - Supports combinational as well as unit testing paradigms - ### Planned - Multiple modifier chaining - Replaceable/extensible assertion library - ## Contents - [test.js](#testjs) - [Features](#features) - [Planned](#planned) - [Contents](#contents) - [Architecture](#architecture) - [Combinational testing](#combinational-testing) - [Unit testing](#unit-testing) - [Installation](#installation) - [Basic usage](#basic-usage) - [CLI](#cli) - [Components](#components) - [`DEFAULT_TEST_FILES`](#default_test_files) - [`IGNORE_TEST_FILES`](#ignore_test_files) - [`Merged`](#merged) - [`.create(..)`](#mergedcreate) - [`.members`](#mergedmembers) - [`.size` / `.usize`](#mergedsize--mergedusize) - [`.add(..)` / `.remove(..)`](#mergedadd--mergedremove) - [`.clear()`](#mergedclear) - [`.keys(..)` / `.values(..)` / `.entries(..)`](#mergedkeys--mergedvalues--mergedentries) - [`.toObject(..)`](#mergedtoobject) - [`.checkShadowing(..)`](#mergedcheckshadowing) - [`.handleShadowing(..)`](#mergedhandleshadowing) - [`.filename`](#memberfilename) - [`TestSet`](#testset) - [`BASE_TEST_SET`](#base_test_set) - [`Setups(..)` / `Setup(..)` (Merged)](#setups--setup-merged) - [`Modifiers(..)` / `Modifier(..)` (Merged)](#modifiers--modifier-merged) - [`Tests(..)` / `Test(..)` (Merged)](#tests--test-merged) - [`Cases(..)` / `Case(..)` (Merged)](#cases--case-merged) - [`Assert(..)`](#assert) - [`run(..)`](#run) - [Advanced components](#advanced-components) - [`runner(..)`](#runner) - [`parser(..)`](#parser) - [Utilities](#utilities) - [`getCallerFilename()`](#getcallerfilename) - [License](#license) ## Architecture This package implements two testing schemes: - Combinational testing Here the user sets up a set of `Setup`, `Modifier` and `Test` functions and the system chains different combinations of the three and runs them. - Unit testing Simple independent tests. ### Combinational testing In general the idea here is that you define three things: - `Setups` that build a test context and objects (the _setup_), - `Modifiers` that modify the _setup_ in some way, - `Tests` that test some aspect of a _setup_. The system builds chains in the form: ``` setup -> modifier* -> test ``` Where `modifier` can me either single or a chain of modifiers. A `setup` and `modifier` can also include assertions/tests for direct testing and sanity checks. The system defines a blank pass-through modifier and test, to alleviate the requirement on the user to define at least one modifier and test, so in cases where it makes no sense only a setup is required. Note that each element can reference and re-use other elements so for example a modifier can call (re-use) other modifiers to avoid code duplication. This makes it simple to define procedural/generative tests. ### Unit testing This is the traditional self-contained test approach. ## Installation ```shell_session $ npm install -i ig-test ``` And to install the global CLI interface ```shell_session $ npm install -g ig-test ``` ## Basic usage Create a test script ```shell_session $ touch test.js $ chmod +x test.js ``` Note that the test script should be named either `"test.js"` or `"-test.js"` for the system to find it automatically. The code: ```javascript #!/usr/bin/env node var test = require('ig-test') // XXX add assert examples... test.Setups({ state: function(assert){ return { a: 123, b: 321, } }, }) test.Modifiers({ inc: function(assert, state){ Object.keys(state) .forEach(function(k){ state[k] += 1 }) return state }, }) test.Tests({ }) test.Cases({ }) // make the test runnable as a standalone script... __filename == (require.main || {}).filename && tests.run() ``` Run the tests ```shell_session $ node ./test.js ``` or ```shell_session $ runtests ``` ## CLI ```shell_session $ npm install -g ig-test ``` Basic help ```shell_session $ runtests --help Usage: test.js [OPTIONS] [CHAIN] ... Run tests. Tests run by test.js can be specified in one of the following formats: : :: Each of the items in the test spec can be a "*" indicating that all relevant items should be used, for example: $ ./test.js basic:*:* Here test.js is instructed to run all tests and modifiers only on the basic setup. Zero or more sets of tests can be specified. When no tests specified test.js will run all tests. Options: -h, --help - print this message and exit -v, --version - show test.js verion and exit -l, --list=PATH - list available tests; note that if passing files via -f explicitly they must precede the -l/-list flag; this has the same defaults as -f --list-found=PATH - like -list but print found test modules and exit -f, --test-file=PATH - test script or filename pattern, supports glob; this flag can be given multiple times for multiple paths/patterns (default: **/?(*-)test.js) -i, --ignore=PATH - path/pattern to ignore in test file search (default: node_modules/**) --verbose - verbose mode (env: $VERBOSE) Examples: $ ./test.js - run all tests. $ ./test.js basic:*:* - run all tests and modifiers on "basic" setup. (see test.js -l for more info) $ ./test.js -v example - run "example" test in verbose mode. $ ./test.js native:gen3:methods init:gen3:methods - run two tests/patterns. ``` List available test components ```shell_session $ runtests --list ``` XXX chains XXX notes on coverage ## Components ### `DEFAULT_TEST_FILES` [`glob`][glob] pattern(s) used to find test files by default. ``` DEFAULT_TEST_FILES = undefined | | [ , .. ] ``` Default value: `"**/?(*-)test.js"` ### `IGNORE_TEST_FILES` A list of [`glob`][glob] patterns to ignore while searching for tests. ``` IGNORE_TEST_FILES = undefined | [ , .. ] ``` Default value: `['node_modules/**']` ### `Merged` Implements a _merged_ collection of instances (_members_). Create a new collection: ``` Merged.create() -> ``` Add members to collection: ``` ({ : , .. }) -> (, ) -> ``` On construction this will assign the input object / ``-`` into the resulting ``/instance object. Each ``/instance created is added to the constructor as a _member_ (i.e. added into `.members`) Provides a set of methods and properties to access/introspect the _merged_ (hence the name) attributes of the _members_ (i.e. `.keys(..)`, `.values(..)`, `.entries(..)`, `.size`/`.usize` and `.members`). Note that though `Merged` itself is a collection, it is not designed to be used directly as a collection, use it to create new collections or as a prototype for inheritance. #### `.create(..)` Create a new `Merged` collection (member constructor). ``` Merged.create() -> ``` #### `.members` List of _members_ / instances of `Merged` in order of creation. #### `.size` / `.usize` Number of _members_ including the `"-"` members and not including respectively. #### `.add(..)` / `.remove(..)` Add / remove a member. ``` .add() -> .remove() -> ``` #### `.clear()` Remove (_clear_) all the members. #### `.keys(..)` / `.values(..)` / `.entries(..)` ``` .keys() .keys() -> .values() .values() -> .entries() .entries() -> ``` These are similar to `Object.keys(..)` / `Object.values(..)` / `Object.entries(..)` but will also if called without arguments return a list of the callers member keys/values/entries respectively. Note that members' attributes can _shadow_ previous member attributes, only one value per key will be returned. `` will warn when adding a member of its attributes will _shadow_ already existing members' attributes (see: [`.checkShadowing(..)`](#mergedcheckshadowing) and [`.handleShadowing(..)`](#mergedhandleshadowing)); Also note that the check for shadowing is performed when the `` is created and not when new attributes are added manually. #### `.toObject(..)` Create an object containing all visible member attributes. ``` .toObject() -> ``` #### `.checkShadowing(..)` Find all shadowed attributes within ``. ``` .checkShadowing() ``` Find all attributes in `` that will be shadowed by `` ``` .checkShadowing() -> ``` #### `.handleShadowing(..)` Will be called on `` construction when attribute _shadowing_ is detected. ``` `.handleShadowing()` -> ``` By default this will print a warning and continue, but can be overloaded by the user to react to _shadowing_ in a different manner. #### `.filename` The filename where the `` was defined. ### `TestSet` XXX ### `BASE_TEST_SET` XXX ### `Setups(..)` / `Setup(..)` (Merged) XXX A _subclass_ or rather _sub-constructor_ of `Merged`. Note that `Setups` and `Setup` are references to the same object, they exists for better readability in cases when we add a single element (``-`` pair) or a bunch of elements (object), for example: ```javascript // single element... test.Setup('some-setup', function(){ // ... }) // arbitrary number of elements... test.Setups({ 'some-other-setup': function(){ // ... }, // ... }) ``` ### `Modifiers(..)` / `Modifier(..)` (Merged) XXX A _sub-constructor_ of `Merged`. ### `Tests(..)` / `Test(..)` (Merged) XXX A _sub-constructor_ of `Merged`. ### `Cases(..)` / `Case(..)` (Merged) XXX A _sub-constructor_ of `Merged`. ### `Assert(..)` XXX this may still change... ### `run(..)` Run the test system. ``` run() run() run() run(, ) -> ``` This will: - parse `process.argv` - locate and run tests - report basic stats `` format: ``` { setups: , modifiers: , tests: , cases: , } ``` ## Advanced components ### `runner(..)` The default test combinator and runner. ### `parser(..)` The default [`ig-argv`][ig-argv] parser setup. ## Utilities ### `getCallerFilename()` Returns the filename of the module where `getCallerFilename()` is called. ## License [BSD 3-Clause License](./LICENSE) Copyright (c) 2016-2020, Alex A. Naanou, All rights reserved. [glob]: https://github.com/isaacs/node-glob [object.js]: https://github.com/flynx/object.js [ig-argv]: https://github.com/flynx/argv.js