# 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 short to mid time frame, use at your own risk, though ideas, feedback and suggestions are welcome. ## Features - Simple / minimalist implementation - Supports combinational as well as unit testing paradigms - Multiple modifier chaining - ### Planned - 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` is optional and can be a chain of zero or more 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. If no tests are specified or '**' is given test.js will run all found tests: $ ./test.js or: $ ./test.js '**' Note that using '*' or '**' may require escaping as they may get expanded by the shell. Options: -h, --help - print this message and exit --version - show test.js version 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 -m, --max-modifier-chain=NUMBER - Maximum number of modifiers to use in chain (default: 1) -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/**"]) -v, --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. Written by: Alex A. Naanou Version: 1.6.7 / License: BSD-3-Clause ``` 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. - Multiple modifier chaining ## License [BSD 3-Clause License](./LICENSE) Copyright (c) 2020-2023, 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