Bruker:Jeblad/Module:BDD

Fra Wikipedia, den frie encyklopedi
Dokumentasjon
Note that the module is not ready for production, it is still under active development!

The purpose of this module is to support behavior-driven development (BDD), a software development process that emerged from test-driven development (TDD), in on-going development of Lua-based modules. It makes an assumption of a test module on a separate page, possibly a subpage, and presentation on another page like the talk page or generated through an API call.

The module mimics some ideas from other BDD libs, like RSpec [1], Jasmine [2], and Busted [3]. There are no clear standard on how to do this, so there are some variation and adaption.

The module is not built for great speed, it uses closures to build a number of small objects, that is methods returns a value that holds a closure. This creates a fairly efficient implementation for simple access, but it is not very efficient for caching larger structures. Some larger structures are put in tables and built once. This is done for Expect where each new instance would otherwise create a lot of methods.

The module avoids as much caching as possible, as this can poison the tests.

Usage[rediger | rediger kilde]

If you have a module like «Module:HelloWorld», the ubiquitous and quite pesky example, coded as something like

local p = {}

function p.helloWorld()
	return "Hi there!"
end

return p

Then on a test page you would test this like like the following

require 'Module:BDD'()
local p = require 'Module:HelloWorld'()

describe('Hello world', function()
	context('On all pages', function()
		it('says hello', function()
			expect('p.helloWorld()', p.helloWorld()):toBe("Hi there!");
		end);
	end);
end);

return result('Module:HelloWorld')

In the first line the test framework is not only loaded, but it is also installed in the global name space. The trailing parenthesis is a call on the loaded table, and it will install additional global functions. It is not necessary to keep a pointer to the test framework, the functions keep their own pointers.

In the second line the module under test is loaded. Usually it is necessary to keep a pointer to the returned value.

The describe (alt Given) function adds a title and evaluates the function from a xpcall (not done yet). The same happens with context (alt When) and it (alt Then). These three are really the same, it is only our interpretation that changes.

The expect function adds a comment and the statement that creates the actual value. It then builds a closure and returns a structure in a flow style. This makes it possible to manipulate the instance, before we finally evaluates it and stores the result for later visualization. In this case we simply tests it to see if it is equal.

The final results are returned by a call to result. Like before this can add a title.

The tests can be prepared for for localization into several languages, for example English and Norwegian Bokmål

require 'Module:BDD'()
local p = require 'Module:HelloWorld'()

describe({en='Hello world', nb='Hallo verden'}, function()
	context({en='on all pages', nb='på alle sider'}, function()
		it({en='says hello', nb='sier hallo'}, function()
			expect('p.helloWorld()', p.helloWorld()):toBe("Hi there!");
		end);
	end);
end);

return result({ en='Module:HelloWorld', nb='Module:HelloWorld'})

In this case the languages will be chosen according to the site content language, and if that isn't defined it will use the languages from the fallback chain. If the first argument is a string, then that string will always be used. The language can be changed if the tests are run through the api, which makes it easy to check tests if they are prepared for the requested language.

Usually it is good practice to both use a common language to make the tests readable and reusable, and use a local language to make them readable for the local community at each project.

Further work[rediger | rediger kilde]

At present formatting does not work properly. It is expected to be fixed. ;)

Setup/teardown[rediger | rediger kilde]

There will be a solution whereby setup and teardown is chained. That means that the tests are somewhat isolated from each other at each test level, that is each time we define a describe, context, or it, the environment will be recreated. State can still leak between expectations though.

It is assumed that there will be no global setup and teardown as there is no resource allocation available from pages in Scribunto.

Xpcall[rediger | rediger kilde]

All calls to provided functions should be protected in xpcalls so a single exception does not make the whole test set to fail. A failed function should be marked as such in the report.

Actual function is xpcall, and it will always add a stack trace on our own stack. The entry might be without a message.

This is partially implemented, proper formatting remains unsolved.

Spy on public calls[rediger | rediger kilde]

Note that spying on public calls made before the module is available to the testing regime will not be possible.

Carp
Adds a message to stack without exiting the test, printing the callers name and its arguments.
Cluck
Like Carp, but also prints a stack trace starting one level up.
Croak/Confess
Like Carp, but also stops the running test (the user provided anonymous function). Because it throws an exception it will always trigger a stack trace.

Call would be like [carp|cluck|croak|confess](message, table, method).

Set up of spies are implemented, but they should be removed during teardown. Such cleanup must check if the struct is still available. This may happen if structs are created inside the test environment.

Coverage for public call[rediger | rediger kilde]

It should be possible to register spies for profiling coverage on members of a module. A function should loop over all members and register spies for all of them. Coverage can then be calculated for the public members. This will only give coverage of the entry points, but it seems sufficient to get a coarse number for coverage.

Stub public methods[rediger | rediger kilde]

It should be possible to stub public methods from the test page. This will not include private methods. (Is this something that should be part of a BDD module?)

Module return[rediger | rediger kilde]

At present an explicit return must be done. It seems to be possible to do an implicit return. This makes a slightly less error prone setup procedure.

Several options exist in literature, but it might be some limitations to what is actually allowed in Scribunto. It seems like all options are blocked.

Test Anything Protocol[rediger | rediger kilde]

At some point the Test Anything Protocol (TAP) should be supported. The simplest solution is to use yet another page for that response, but it is also possible to test on the page name. This can be different if we transclude the talk page into some other page, or even constructs the page on the fly through the api. If the transcluded page name is "API" then we switch to a TAP response. This makes it possible to support several formats from a single page.

Support libs[rediger | rediger kilde]

See also[rediger | rediger kilde]


-- module for Behavior Driven Development testing framework
-- © John Erling Blad, Creative Commons by Attribution 3.0

local util = require('Modul:BDD/utils')
local view = require('Modul:BDD/view')

-- @var The table holding the modules exported members
local bdd = {}
	
local function buildName( str )
	return str:gsub("([A-Z])", function(str) return '-'..string.lower(str) end )
end

-- constructor for a closure to make the functions use the same stack
local function BDD()
	-- @var This is the stack where we store our temporary formatters
	local stack = { "result", '' }
	
	-- @var This is the accumulators where we store our statistics
--	local statistics = { "statistics", { "accumulator" , 0, 0 } }
	
	-- @var accumulator for failed tests
	local numFailed = 0
	
	-- @var base for expectations
	local base = {}
	
	-- @var list of preprocess functions
	-- local preprocess = {}
	
	-- function to make the chainable preprocess method
	-- @param name identifier for the method
	-- @param func the code to be run
	-- @return self to make the method chainable
	local function makePreProcess( name, func )
		base[name] = function( self )
			self.preprocess[1+#self.preprocess] = { buildName( name ), func }
			return self
		end
	end
	
	-- preprocess a string to make it all upper case
	-- @param str string that will be upper cased
	-- @return string that is upper cased
	makePreProcess( 'asUpper',
		function ( str )
			return str:upper()
		end
	)
	-- @alias upper
	base.upper = base.asUpper
	-- @alias asUC
	base.asUC = base.asUpper
	-- @alias uc
	base.uc = base.asUpper
	
	-- preprocess a string to make it all lowerr case
	-- @param str string that will be lower cased
	-- @return string that is lower cased
	makePreProcess( 'asLower',
		function ( str )
			return str:lower()
		end
	)
	-- @alias lower
	base.lower = base.asLower
	-- @alias asLC
	base.asLC = base.asLower
	-- @alias lc
	base.lc = base.asLower
		
	-- preprocess a string to make the first letter upper case
	-- @param str string that will have the first letter upper cased
	-- @return string with the first letter upper cased
	makePreProcess( 'asUpperFirst',
		function ( str )
			return str:sub(1,1):upper()..str:sub(2)
		end
	)
	-- @alias upperfirst
	base.upperfirst = base.asUpperFirst
	-- @alias asUCFirst
	base.asUCFirst = base.asUpperFirst
	-- @alias asUcFirst
	base.asUcFirst = base.asUpperFirst
	-- @alias asUCfirst
	base.asUCfirst = base.asUpperFirst
	-- @alias ucfirst
	base.ucfirst = base.asUpperFirst
		
	-- preprocess a string to make the first letter lower case
	-- @param str string that will have the first letter lower cased
	-- @return string with the first letter lower cased
	makePreProcess( 'asLowerFirst',
		function ( str )
			return str:sub(1,1):lower()..str:sub(2)
		end
	)
	-- @alias lowerfirst
	base.lowerfirst = base.asLowerFirst
	-- @alias asLCFirst
	base.asLCFirst = base.asLowerFirst
	-- @alias asLcFirst
	base.asLcFirst = base.asLowerFirst
	-- @alias asLCfirst
	base.asLCfirst = base.asLowerFirst
	-- @alias lcfirst
	base.lcfirst = base.asLowerFirst
		
	-- preprocess a string with an identity function
	-- @param str string that will be the source for the identity
	-- @return string that is the identity
	makePreProcess( 'asIdentity',
		function ( str )
			return str
		end
	)

	-- @var list of postprocess functions
	--local postprocess = {}
	
	-- function to make the chainable postprocess method
	-- @param name identifier for the method
	-- @param func the code to be run
	-- @return self to make the method chainable
	local function makePostProcess( name, func )
		base[name] = function( self )
			self.postprocess[1+#self.postprocess] = { buildName( name ), func }
			return self
		end
	end

	-- postprocess a value with an identity function
	-- @param bool boolean that will be the source for the identity
	-- @return boolean that is the identity
	makePostProcess( 'whenIdentity',
		function ( bool )
			return bool
		end
	)

	-- postprocess a value with an invert function
	-- @param bool boolean that will be the source for the inversion
	-- @return boolean that is the inverted
	makePostProcess( 'whenInvert',
		function ( bool )
			return not bool
		end
	)
	-- @alias invert
	base.invert = base.whenInvert
	-- @alias inverted
	base.inverted = base.whenInvert
	-- @alias whenInv
	base.whenInv = base.whenInvert
	-- @alias inv
	base.inv = base.whenInvert
	
	-- @var list of similarity functions
	local similarity = {}
	
	-- function to make the chainable similarity method
	-- @param name identifier for the method
	-- @param func the code to be run
	-- @return self to make the method chainable
	local function makeSimilarity( name, func )
		similarity[name] = func
		base[name] = function( self )
			self.similarity = { buildName( name ), func }
			return self
		end
	end

	-- similarity of the type test with boolean type
	-- @param optional any value that will be the source (actual) value for the test
	-- @return boolean that is the result of the type test
	makeSimilarity( 'ifBoolean',
		function ( a )
			return type( a ) == 'boolean'
		end
	)
	-- @alias Boolean
	base.ifboolean = base.ifBoolean

	-- similarity of the type test with string type
	-- @param optional any value that will be the source (actual) value for the test
	-- @return boolean that is the result of the type test
	makeSimilarity( 'ifString',
		function ( a )
			return type( a ) == 'string'
		end
	)
	-- @alias String
	base.ifstring = base.ifString

	-- similarity of the type test with number type
	-- @param optional any value that will be the source (actual) value for the test
	-- @return boolean that is the result of the type test
	makeSimilarity( 'ifNumber',
		function ( a )
			return type( a ) == 'number'
		end
	)
	-- @alias Number
	base.ifnumber = base.ifNumber

	-- similarity of the type test with table type
	-- @param optional any value that will be the source (actual) value for the test
	-- @return boolean that is the result of the type test
	makeSimilarity( 'ifTable',
		function ( a )
			return type( a ) == 'table'
		end
	)
	-- @alias Table
	base.iftable = base.ifTable

	-- similarity of the type test with nil type
	-- @param optional any value that will be the source (actual) value for the test
	-- @return boolean that is the result of the type test
	makeSimilarity( 'ifNil',
		function ( a )
			return type( a ) == 'nil'
		end
	)
	-- @alias Nil
	base.ifnil = base.ifNil

	-- similarity method that will always fail
	-- @param optional any value that will be the source (actual) value for the comparison
	-- @param optional any value that will be the target (expected) value for the comparison
	-- @return boolean that is the result of the comparison
	makeSimilarity( 'ifFail',
		function ( a, b )
			return false
		end
	)
	-- @alias Fail
	base.iffail = base.ifFail

	-- similarity done as an equal operation between the first and second argument
	-- @param optional any value that will be the source (actual) value for the comparison
	-- @param optional any value that will be the target (expected) value for the comparison
	-- @return boolean that is the result of the comparison
	makeSimilarity( 'ifEqual',
		function ( a, b )
			return a == b
		end
	)
	-- @alias equal
	base.equal = base.ifEqual
	
	-- similarity done as a deep equal operation between the first and second argument
	-- @param optional any value that will be the source (actual) value for the comparison
	-- @param optional any value that will be the target (expected) value for the comparison
	-- @return boolean that is the result of the comparison
	makeSimilarity( 'ifDeepEqual',
		function ( a, b )
			return util.deepEqual( a, b)
		end
	)
	-- @alias deepequal
	base.deepequal = base.ifDeepEqual

	-- similarity done as a lesser than operation between the first and second argument
	-- @param optional any value that will be the source (actual) value for the comparison
	-- @param optional any value that will be the target (expected) value for the comparison
	-- @return boolean that is the result of the comparison
	makeSimilarity( 'ifLesserThan',
		function ( a, b )
			return a < b
		end
	)
	-- @alias ifLesser
	base.ifLesser = base.ifLesserThan
	-- @alias ifLT
	base.ifLT = base.ifLesserThan
	-- @alias lesser
	base.lesser = base.ifLesserThan

	-- similarity done as a greater than operation between the first and second argument
	-- @param optional any value that will be the source (actual) value for the comparison
	-- @param optional any value that will be the target (expected) value for the comparison
	-- @return boolean that is the result of the comparison
	makeSimilarity( 'ifGreaterThan',
		function ( a, b )
			return a > b
		end
	)
	-- @alias ifGreater
	base.ifGreater = base.ifGreaterThan
	-- @alias ifGT
	base.ifGT = base.ifGreaterThan
	-- @alias greater
	base.greater = base.ifGreaterThan

	-- similarity done as a lesser or equal than operation between the first and second argument
	-- @param optional any value that will be the source (actual) value for the comparison
	-- @param optional any value that will be the target (expected) value for the comparison
	-- @return boolean that is the result of the comparison
	makeSimilarity( 'ifLesserOrEqual',
		function ( a, b )
			return a <= b
		end
	)
	-- @alias ifLesserEqual
	base.ifLesserEqual = base.ifLesserOrEqual
	-- @alias ifLE
	base.ifLE = base.ifLesserOrEqual
	-- @alias lesserequal
	base.lesserequal = base.ifLesserOrEqual

	-- similarity done as a greater or equal than operation between the first and second argument
	-- @param optional any value that will be the source (actual) value for the comparison
	-- @param optional any value that will be the target (expected) value for the comparison
	-- @return boolean that is the result of the comparison
	makeSimilarity( 'ifGreaterOrEqual',
		function ( a, b )
			return a >= b
		end
	)
	-- @alias ifGreaterEqual
	base.ifGreaterEqual = base.ifGreaterOrEqual
	-- @alias ifGE
	base.ifGE = base.ifGreaterOrEqual
	-- @alias greaterequal
	base.greaterequal = base.ifGreaterOrEqual

	-- similarity done as a strict boolean false on the argument
	-- @param optional any value that will be the source (actual) value for the type-value test
	-- @return boolean that is the result of the test
	makeSimilarity( 'ifFalse',
		function ( a )
			return type(a) == 'boolean' and a == false
		end
	)
	-- @alias False
	base.False = base.ifFalse

	-- similarity done as a strict boolean true on the argument
	-- @param optional any value that will be the source (actual) value for the type-value test
	-- @return boolean that is the result of the test
	makeSimilarity( 'ifTrue',
		function ( a )
			return type(a) == 'boolean' and a == true
		end
	)
	-- @alias True
	base.True = base.ifTrue

	-- similarity done as a loose boolean false on the argument
	-- @param optional any value that will be the source (actual) value for the value test
	-- @return boolean that is the result of the test
	makeSimilarity( 'ifFalsy',
		function ( a )
			return not a
		end
	)
	-- @alias falsy
	base.falsy = base.ifFalsy

	-- similarity done as a loose boolean true on the argument
	-- @param optional any value that will be the source (actual) value for the value test
	-- @return boolean that is the result of the test
	makeSimilarity( 'ifTruthy',
		function ( a )
			return not not a
		end
	)
	-- @alias truthy
	base.truthy = base.ifTruthy

	-- fake similarity done as an identity on the first argument
	-- @param optional any value that will be the source (actual) value for the comparison
	-- @param optional any value that will be the target (expected) value for the comparison
	-- @return any that is the first argument
	makeSimilarity( 'ifFirstIdentity',
		function ( a, b )
			return a
		end
	)
	-- @alias firstIdentity
	base.firstIdentity = base.ifFirstIdentity

	-- fake similarity done as an identity on the second argument
	-- @param optional any value that will be the source (actual) value for the comparison
	-- @param optional any value that will be the target (expected) value for the comparison
	-- @return any that is the second argument
	makeSimilarity( 'ifSecondIdentity',
		function ( a, b )
			return b
		end
	)
	-- @alias secondIdentity
	base.secondIdentity = base.ifSecondIdentity

	-- similarity done as a string match for the target inside the source string
	-- @param optional any value that will be the source (actual) value 
	-- @param optional any value that will be the target (expected) value 
	-- @return string that is the first result from the match
	makeSimilarity( 'ifMatch',
		function ( a, b ) local res = string.match(a, b);
			return res or false
		end
	)
	-- @alias match
	base.match = base.ifMatch

	-- similarity done as an ustring match for the target inside the source string
	-- @param optional any value that will be the source (actual) value 
	-- @param optional any value that will be the target (expected) value 
	-- @return string that is the first result from the match
	makeSimilarity( 'ifUstringMatch',
		function ( a, b ) local res = mw.ustring.match(a, b); 
			return res or false
		end
	)
	-- @alias umatch
	base.umatch = base.ifUstringMatch

	-- similarity done as a search for the target inside the source table
	-- @param optional any value that will be the source (actual) value 
	-- @param optional any value that will be the target (expected) value 
	-- @return [false|number] that is the position of the found item, or false
	makeSimilarity( 'ifContains',
		function ( a, b )
			return util.contains( a, b )
		end
	)
	-- @alias ifContain
	base.ifContain = base.ifContains
	-- @alias contains
	base.contains = base.ifContains
	-- @alias contain
	base.contain = base.ifContains

	-- similarity done as a test for the source value inside the limits of the target
	-- @param optional any value that will be the source number (actual) value 
	-- @param optional any value that will be the target number (expected) value 
	-- @param optional any value that will be the error on the target value
	-- @return boolean that is the result of the comparison
	makeSimilarity( 'ifCloseTo',
		function ( a, b, c )
			return b-c <= a and a <= b+c
		end
	)
	-- @alias closeTo
	base.closeTo = base.ifCloseTo
			
	-- @var list of similarity functions
	local matchers = {}
	
	-- function to make the chainable matcher method
	-- @param name identifier for the method
	-- @param func the code to be run
	-- @return self to make the method chainable
	local function makeMatcher( name, func )
		base[name] = function( self, ... )
			if func then
				self.similarity = { buildName( name ), func }
			end
			local res = self.exec( ... )
			-- @todo must be wrapped up and made html safe
			-- str comes from the constructor
			local rep = { "expect", self.str, { "params", ... }, {}, unpack( res ) }
			if not res then
				numFailed = 1+numFailed
				--rep[1+#rep] = debug.traceback()
			end
			stack[1+#stack] = rep
			return self
		end
	end

	-- matching done with the stored operation between actual and expected value
	-- the default match is with an equal operation
	-- @param optional any value that will be the target (expected) value for the comparison
	-- @return boolean that is the result of the comparison
	makeMatcher(
		'toBe',
		false
	)
	-- @alias be
	base.be = base.toBe
	-- @alias beEqual
	base.beEqual = base.toBe
	-- @alias toEqual
	base.toEqual = base.toBe
	
	-- matching done with a type operation on the actual and 'boolean' as expected value
	-- @return boolean that is the result of the type test
	makeMatcher(
		'toBeBoolean',
		similarity['ifBoolean']
	)
	-- @alias beBoolean
	base.beBoolean = base.toBeBoolean
	-- @alias toBoolean
	base.toBoolean = base.toBeBoolean
	-- @alias Boolean
	base.Boolean = base.toBeBoolean
	
	-- matching done with a type operation on the actual and 'string' as expected value
	-- @return boolean that is the result of the type test
	makeMatcher(
		'toBeString',
		similarity['ifString']
	)
	-- @alias beString
	base.beString = base.toBeString
	-- @alias toString
	base.toString = base.toBeString
	-- @alias String
	base.String = base.toBeString
	
	-- matching done with a type operation on the actual and 'number' as expected value
	-- @return boolean that is the result of the type test
	makeMatcher(
		'toBeNumber',
		similarity['ifNumber']
	)
	-- @alias beNumber
	base.beNumber = base.toBeNumber
	-- @alias toNumber
	base.toNumber = base.toBeNumber
	-- @alias Number
	base.Number = base.toBeNumber
	
	-- matching done with a type operation on the actual and 'table' as expected value
	-- @return boolean that is the result of the type test
	makeMatcher(
		'toBeTable',
		similarity['ifTable']
	)
	-- @alias beTable
	base.beTable = base.toBeTable
	-- @alias toTable
	base.toTable = base.toBeTable
	-- @alias Table
	base.Table = base.toBeTable
	
	-- matching done with a type operation on the actual and 'nil' as expected value
	-- @return boolean that is the result of the type test
	makeMatcher(
		'toBeNil',
		similarity['ifNil']
	)
	-- @alias beNil
	base.beNil = base.toBeNil
	-- @alias toNil
	base.toNil = base.toBeNil
	-- @alias Nil
	base.Nil = base.toBeNil
	
	-- matching done with a deep equality operation on the actual and expected value
	-- @param optional any value that will be the target (expected) value
	-- @return boolean that is the result of the comparison
	makeMatcher(
		'toBeDeepEqual',
		similarity['ifDeepEqual']
	)
	-- @alias beDeepEqual
	base.beDeepEqual = base.toBeDeepEqual
	-- @alias
	base.toDeepEqual = base.toBeDeepEqual

	-- matching done in a way that will always fail
	-- @return boolean that is the result of the comparison
	makeMatcher(
		'toBeFailing',
		similarity['ifFail']
	)
	-- @alias beFailing
	base.beFailing = base.toBeFailing
	-- @alias toFailing
	base.toFailing = base.toBeFailing
	-- @alias Failing
	base.Failing = base.toBeFailing
	
	-- matching done with a lesser than operation on the actual and expected value
	-- @param optional any value that will be the target (expected) value
	-- @return boolean that is the result of the comparison
	makeMatcher(
		'toBeLesserThan',
		similarity['ifLesserThan']
	)
	
	-- matching done with a greater than operation on the actual and expected value
	-- @param optional any value that will be the target (expected) value
	-- @return boolean that is the result of the comparison
	makeMatcher(
		'toBeGreaterThan',
		similarity['ifGreaterThan']
	)
	
	-- matching done with a lesser or equal operation on the actual and expected value
	-- @param optional any value that will be the target (expected) value
	-- @return boolean that is the result of the comparison
	makeMatcher(
		'toBeLesserOrEqual',
		similarity['ifLesserOrEqual']
	)
	
	-- matching done with agreater or equal operation on the actual and expected value
	-- @param optional any value that will be the target (expected) value
	-- @return boolean that is the result of the comparison
	makeMatcher(
		'toBeGreaterOrEqual',
		similarity['ifGreaterOrEqual']
	)
	
	-- matching done with a loose equality operation on the actual and 'false' as expected value
	-- @param optional any value that will be the target (expected) value
	-- @return boolean that is the result of the comparison
	makeMatcher(
		'toBeFalsy',
		similarity['ifFalsy']
	)
	-- @alias beFalsy
	base.beFalsy = base.toBeFalsy
	-- @alias toFalsy
	base.toFalsy = base.toBeFalsy
	-- @alias Falsy
	base.Falsy = base.toBeFalsy
	
	-- matching done with a loose equality operation on the actual and 'true' as expected value
	-- @param optional any value that will be the target (expected) value
	-- @return boolean that is the result of the comparison
	makeMatcher(
		'toBeTruthy',
		similarity['ifTruthy']
	)
	-- @alias beTruthy
	base.beTruthy = base.toBeTruthy
	-- @alias toTruthy
	base.toTruthy = base.toBeTruthy
	-- @alias Truthy
	base.Truthy = base.toBeTruthy
	
	-- matching done with an equality operation on the actual and 'false' as expected value
	-- @param optional any value that will be the target (expected) value
	-- @return boolean that is the result of the comparison
	makeMatcher(
		'toBeFalse',
		similarity['ifFalse']
	)
	-- @alias beFalse
	base.beFalse = base.toBeFalse
	-- @alias toFalse
	base.toFalse = base.toBeFalse
	-- @alias False
	base.False = base.toBeFalse
	
	-- matching done with an equality operation on the actual and 'true' as expected value
	-- @param optional any value that will be the target (expected) value
	-- @return boolean that is the result of the comparison
	makeMatcher(
		'toBeTrue',
		similarity['ifTrue']
	)
	-- @alias beTrue
	base.beTrue = base.toBeTrue
	-- @alias toTrue
	base.toTrue = base.toBeTrue
	-- @alias True
	base.True = base.toBeTrue
	
	-- fake matching that returns the actual value
	-- @param optional any value that will be thrown away
	-- @return any value that is used as the actual
	makeMatcher(
		'toBeFirstIdentity',
		similarity['ifFirstIdentity']
	)
	
	-- fake matching that returns the expected value
	-- @param optional any value that will be thrown away
	-- @return any value that is used as the actual
	makeMatcher(
		'toBeSecondIdentity',
		similarity['ifSecondIdentity']
	)
	
	-- matching done with a match for the target (expected) inside source (actual) string
	-- @param optional any value that will be the target (expected) for the match
	-- @return string that is the first result from the match
	makeMatcher(
		'toBeMatch',
		similarity['ifMatch']
	)
	-- @alias beMatch
	base.beMatch = base.toBeMatch
	-- @alias toMatch
	base.toMatch = base.toBeMatch
	
	-- matching done with an ustring match for the target (expected) inside source (actual) string
	-- @param optional any value that will be the target (expected) for the match
	-- @return string that is the first result from the match
	makeMatcher(
		'toBeUstringMatch',
		similarity['ifUstringMatch']
	)
	-- @alias beUMatch
	base.beUMatch = base.toBeUstringMatch
	-- @alias toUMatch
	base.toUMatch = base.toBeUstringMatch
	
	-- matching done as a search for the target inside the source table
	-- @param optional any value that will be the target (expected) for the match
	-- @return [false|number] that is the position of the found item, or false
	makeMatcher(
		'toBeContains',
		similarity['ifContains']
	)
	-- @alias toContains
	base.toContains = base.toBeContains
	-- @alias toContain
	base.toContain = base.toBeContains
	-- @alias beContains
	base.beContains = base.toBeContains
	-- @alias beContain
	base.beContain = base.toBeContains

	-- matching done as a test for the source value inside the limits of the target
	-- @param optional any value that will be the target number (expected) value 
	-- @param optional any value that will be the error on the target value
	-- @return boolean that is the result of the comparison
	makeMatcher(
		'toBeCloseTo',
		similarity['ifCloseTo']
	)
	-- @alias toCloseTo
	base.toCloseTo = base.toBeCloseTo
	-- @alias beCloseTo
	base.beCloseTo = base.toBeCloseTo

	-- constructor for expectations
	local function Expect( str, actual )
		
		local self = {}
		
		setmetatable(self, {__index = base})
		
		-- @var list of processes to run before matching
		self.preprocess = {}
		self.str = str
		self.actual = actual 
		
		-- @var similarity process
		self.similarity = {
			buildName('ifEqual'),
			-- following is a default
			function ( a, b ) return a == b end
		}
		
		-- @var list of processes to run after matching
		self.postprocess = {}
		
		function self.exec( ... )
			
			local values = {}
			
			-- this is the actual argument
			values[1+#values] = { 'actual', self.actual }
			
			--this is a chain of preprocesses
			for _,v in ipairs( self.preprocess ) do
				values[1+#values] = { v[1], v[2]( values[#values][2] ) }
			end
			
			local keep = #values
			
			-- this is the anticipated result
			values[1+#values] = { 'anticipated', ... }
			
			-- this is the actual similarity test
			values[1+#values] = { self.similarity[1], self.similarity[2]( values[keep][2], ... ) }
			
			-- this is a chain of postprocesses
			for _,v in ipairs( self.postprocess ) do
				values[1+#values] = { v[1], v[2]( values[#values][2] ) }
			end
			
			return values
		end
		
		-- match for "throw"
		-- @return table
		function self.toThrow()
			return self
		end
		
		-- match for "have been called"
		-- @return table
		function self.toHaveBeenCalled()
			return self
		end
		
		-- match for "have been called with"
		-- @return table
		function self.toHaveBeenCalledWith()
			return self
		end
		
		-- this is our instance
		return self
	end

	-- @var This is the module structure returned from the test set
	local wrap = {
		-- the test runner
		tests = function( frame )
			--return mw.dumpObject( stack )
			return view.Create( stack, {numfailed=numFailed} ).format()
		end,
		categories = function(frame)
			local cats = {
				'[[Category:category-all-tests]]',
				--'[[Category:'..g('category-all-tests')..']]',
			}
			return table.concat(cats, '\n')
		end
	}

	local function stackTrace( msg )
		local tbl = {}
		tbl[1+#tbl] = "stack"
		tbl[1+#tbl] = msg
		-- @todo must be made html safe
		tbl[1+#tbl] = mw.text.split( debug.traceback(), "\n\t", true )
		stack[1+#stack] = tbl
	end

	local function exceptionTrace( msg )
		local tbl = {}
		tbl[1+#tbl] = "exception"
		tbl[1+#tbl] = msg
		-- @todo must be made html safe
		tbl[1+#tbl] = mw.text.split( debug.traceback(), "\n\t", true )
		stack[1+#stack] = tbl
	end
	
	-- this builds a frame and later wraps it up
	local function frame( name )
		-- build and return an appropriate function
		return function( str, func )
		--	statistics[1+#statistics] = { "accumulator", 0, 0 }
			local depth = 1+#stack
			stack[1+#stack] = name
			stack[1+#stack] = str
			xpcall( func, exceptionTrace )
			local tbl = {}
			for i=depth,#stack,1 do
				tbl[1+#tbl] = stack[i]
				stack[i] = nil
			end
		--	local last = statistics[#statistics]
			--table.insert(tbl, 3, last)
			--local prev = statistics[#statistics-1]
			--prev[2] = prev[2]+last[2]
			--prev[3] = prev[3]+last[3]
		--	table.remove(statistics)
			stack[1+#stack] = tbl
		end
	end
	
	-- this builds a xframe and later wraps it up
	local function xframe( name )
		-- build and return an appropriate function
		return function( str, func )
			stack[1+#stack] = { name, str }
		end
	end

	-- this builds spies by monkey patching
	-- note that it seems impossible to store at a funcs location
	-- @todo still needs to clean it up in a teardown, now it sticks
	local function spy( name, trace )
		-- build and return an appropriate function
		return function( str, struct, meth )
			local oldFunc = struct[meth]
			struct[meth] = function(...)
				-- @todo must be wrapped up and made html safe
				stack[1+#stack] = { name, str, { "params", ... } }
				if name == 'cluck' then
					stackTrace( name )
				elseif name == 'croak' then
					error('croak')
				end
				return oldFunc(...)
			end
		end
	end
	
	
	-- @var This is the functions exposed for the tests
	local members = {
		Given = frame('given'),
		When = frame('when'),
		Then = frame('then'),
		describe = frame('describe'),
		context = frame('context'),
		it = frame('it'),
		test = it,
		pending = xframe('pending'),
		xGiven = frame('xgiven'),
		xWhen = frame('xwhen'),
		xThen = frame('xthen'),
		xdescribe = xframe('xdescribe'),
		xcontext = xframe('xcontext'),
		xit = xframe('xit'),
		xtest = xit,
		-- after,
		-- before,
		expect = Expect,
		carp = spy("carp"),
		cluck = spy("cluck", true),
		croak = spy("croak"),
		confess = croak,
		-- @todo not sure about this
		view = view.Create,
		result = function(head)
			stack[2] = head
			return wrap
		end,
		stack = function()
			return stack
		end,
		similarity = function()
			return similarity
		end
	}
	
	return members
end

-- metatable for the export
local mt = {
	-- adjust the installation of the module
	__call = function ()
		_G['_BDD_TEST'] = true
		local t = BDD()
		for k,v in pairs(t) do
			_G[k] = v
		end
		return bdd
	end
}

-- install the metatable
setmetatable(bdd, mt)

return bdd