• Stars
    star
    366
  • Rank 116,547 (Top 3 %)
  • Language
    C++
  • License
    Boost Software Li...
  • Created over 11 years ago
  • Updated over 1 year ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

A modern, C++11-native, single-file header-only, tiny framework for unit-tests, TDD and BDD (includes C++98 variant)

lest โ€“ lest errors escape testing

Language Standard Standard License Build Status Build status Version download Conan Try it online

This tiny C++11 test framework is based on ideas and examples by Kevlin Henney [1,2] and on ideas found in the CATCH test framework by Phil Nash [3].

Let writing tests become irresistibly easy and attractive.

Contents

Example usage

#include "lest/lest.hpp"

using namespace std;

const lest::test specification[] =
{
    CASE( "Empty string has length zero (succeed)" )
    {
        EXPECT( 0 == string(  ).length() );
        EXPECT( 0 == string("").length() );
    },

    CASE( "Text compares lexically (fail)" )
    {
        EXPECT( string("hello") > string("world") );
    },

    CASE( "Unexpected exception is reported" )
    {
        EXPECT( (throw std::runtime_error("surprise!"), true) );
    },

    CASE( "Unspecified expected exception is captured" )
    {
        EXPECT_THROWS( throw std::runtime_error("surprise!") );
    },

    CASE( "Specified expected exception is captured" )
    {
        EXPECT_THROWS_AS( throw std::bad_alloc(), std::bad_alloc );
    },

    CASE( "Expected exception is reported missing" )
    {
        EXPECT_THROWS( true );
    },

    CASE( "Specific expected exception is reported missing" )
    {
        EXPECT_THROWS_AS( true, std::runtime_error );
    },
};

int main( int argc, char * argv[] )
{
    return lest::run( specification, argc, argv );
}

Note: besides above table approach, lest also supports auto-registration of tests.

Compile and run

prompt>g++ -Wall -Wextra -std=c++11 -I../include -o 05_select.exe 05_select.cpp && 05_select.exe
05_select.cpp:17: failed: Text compares lexically (fail): string("hello") > string("world") for "hello" > "world"
05_select.cpp:22: failed: got unexpected exception with message "surprise!": Unexpected exception is reported: (throw std::runtime_error("surprise!"), true)
05_select.cpp:37: failed: didn't get exception: Expected exception is reported missing: true
05_select.cpp:42: failed: didn't get exception of type std::runtime_error: Specific expected exception is reported missing: true
4 out of 7 selected tests failed.

With Buck:

prompt> buck run example/:05_select
...

In a nutshell

lest is a small C++11 test framework for unit testing, regression testing, Test-driven development (TDD) and Behaviour-driven design (BDD). It replicates innovative ideas in C++ testing from the Catch test framework such as function-level fixtures and expression-decomposing assertion macros in a form that is compact enough to read in five minutes. The lest_cpp03 variant provides similar capabilities to use with C++98/03 compilers.

Features and properties of lest are ease of installation (single header), no boilerplate code, traditional unit test cases and BDD style scenarios, strings as test names, function-level fixtures, expression-decomposing assertion macros, support for floating point comparison, test selection from commandline, test duration timing, test randomisation and sorting, display of passing tests, colourised output (compile-time option), C++11 code and a C++98/03 variant with comparable features (also compilable as C++11).

Features available via other projects are mocking (see integrate Trompeloeil mocking framework) and hamcrest matchers (see variants of lest),

Not provided are things present in other test frameworks, such as suites of tests, value-parameterised tests, type-parameterised tests, test data generators, customisable reporting, easy logging of extra information, breaking into a debugger, concurrent execution of tests, isolated execution of tests, Visual Studio Test Adapter.

License

lest uses the Boost Software License.

Dependencies

lest has no other dependencies than the C++ standard library.

Installation

lest is a single-file header-only library. Put lest.hpp, or a variant of it such as lest_cpp03.hpp directly into the project source tree or somewhere reachable from your project.

Usage

Synopsis

Contents

Command line

Usage: test [options] [test-spec ...]

Options:

  • -h, --help, this help message
  • -a, --abort, abort at first failure
  • -c, --count, count selected tests
  • -g, --list-tags, list tags of selected tests
  • -l, --list-tests, list selected tests
  • -p, --pass, also report passing tests
  • -z, --pass-zen, ... without expansion
  • -t, --time, list duration of selected tests
  • -v, --verbose, also report passing or failing sections
  • --order=declared, use source code test order (default)
  • --order=lexical, use lexical sort test order
  • --order=random, use random test order
  • --random-seed=n, use n for random generator seed
  • --random-seed=time, use time for random generator seed
  • --repeat=n, repeat selected tests n times (-1: indefinite)
  • --version, report lest version and compiler used
  • --, end options

Test specification:

  • "@", "*": all tests, unless excluded
  • empty: all tests, unless tagged [hide] or [.optional-name]
  • "text": select tests that contain text (case insensitive)
  • "!text": omit tests that contain text (case insensitive)

Test descriptions can contain tags such as [option], [hide] and [.integration]. Tests that contain the tag [hide] or a tag that starts with [. in their description are skipped, unless they are specifically selected by specifying "@", "*", or by specifying (part of) the tag.

Test specifications can be combined and are evaluated left-to-right. For example: a !ab abc selects all tests that contain 'a', except those that contain 'ab', but include those that contain 'abc'.

When regular expression selection has been enabled (and works), test specifications can use the regular expression syntax of std::regex_search(). See also lest_FEATURE_REGEX_SEARCH in section Other Macros.

Test case macro

A lest test specification can consist of a) one or more arrays of test cases that use lambdas, or b) auto-registered test cases that use free functions. See also macro lest_FEATURE_AUTO_REGISTER.

CASE( "proposition" ) { code } โ€ƒ (array of cases)
Describe the expected behaviour to test for and specify the actions and expectations. See also section Module registration macro โ€“ Single-file code example โ€“ Multi-file code example part 1, 2, 3.

CASE_ON( "proposition", ...) { code } โ€ƒ (array of cases, since v1.33)
Describe the expected behaviour to test for and specify the actions and expectations. After the description you can add a lambda capture list to refer to symbols in the enclosing scope. See also section Module registration macro โ€“ Single-file code example โ€“ Multi-file code example part 1, 2, 3.

lest_CASE( specification, "proposition" ) { code } โ€ƒ (auto-registered cases)
Provide the collection of test cases, describe the expected behaviour to test for and specify the actions and expectations. Consider defining macro CASE(proposition) to hide the collection of test cases and define it in terms of lest_CASE(...) โ€“ Single-file code example โ€“ Multi-file code example part 1, 2, 3.

Fixture macros

lest provides function-level fixtures. Fixtures are stack-based and their setup and teardown occurs at the block scope of SETUP and (nested) SECTIONs โ€“ Code example.

SETUP( "context" ) { code }
Describe and setup the context to use afresh in each enclosed section.

SECTION( "proposition" ) { code }
Describe the expected behaviour to test for using the enclosing context and specify the actions and expectations. The objects in the enclosing setup or section come into existence and go out of scope for each section. A section must be enclosed in setup or in another section.

Assertion macros

lest has expression-decomposing assertion macros. An expression with strings such as hello > world may be reported with code and expansion as hello > world ("hello" > "world"). As a consequence, only a few assertion macro variants are needed โ€“ Code example.

EXPECT( expr )
Evaluate the expression and report failure. If an exception is thrown it is caught, reported and counted as a failure.

EXPECT_NOT( expr )
Evaluate the expression, record the logical not of its result and report failure. If an exception is thrown it is caught, reported and counted as a failure. This macro is a workaround to circumvent ! prefixed expressions as these cannot be decomposed.

EXPECT_NO_THROW( expr )
Expect that no exception (of any type) is thrown during evaluation of the expression.

EXPECT_THROWS( expr )
Expect that an exception (of any type) is thrown during evaluation of the expression.

EXPECT_THROWS_AS( expr, exception )
Expect that an exception of the specified type is thrown during evaluation of the expression.

If an assertion fails, the remainder of the test that assertion is part of is skipped.

BDD style macros

lest provides several macros to write Behaviour-Driven Design (BDD) style scenarios โ€“ Code example, auto-registration.

lest_SCENARIO( specification, "sketch" ) { code } โ€ƒ (auto-registered cases)

SCENARIO( "sketch" ) { code } โ€ƒ (array of cases)

GIVEN( "context" ) { code }

WHEN( "action" ) { code }

THEN( "result" ) { code }

AND_WHEN( "action" ) { code }

AND_THEN( "result" ) { code }

These macros simply map to macros CASE(), SETUP() and SECTION().

For auto-registered scenarios, consider defining macro SCENARIO(proposition) to hide the collection of scenarios and define it in terms of lest_SCENARIO(...).

Module registration macro

When using arrays of test cases written across multiple files, you can use macro MODULE() to add a module's test cases to the overall specification โ€“ Code example part 1, 2, 3.

MODULE( overall-specification, module-specification )
Register this module's test specification with the overall specification.

Note that with lest using auto test case registration there's no need for macro MODULE(), see the auto-registration example part 1, 2, 3. The same holds for lest_cpp03, see cpp03 example part 1, 2, 3.

Feature selection macros

-Dlest_NO_SHORT_MACRO_NAMES
-Dlest_NO_SHORT_ASSERTION_NAMES (deprecated)
All public API macros of lest exist as lest_MACRO and shorthand MACRO variant. Define this macro to omit the shorthand macros.

-Dlest_FEATURE_AUTO_REGISTER=0
Define this to 1 to enable auto registration of test cases. Default is 0.

See also section Test case macro.

-Dlest_FEATURE_COLOURISE=0
Define this to 1 to emphasise success and failure with colour. Default is 0.

Note: ANSI colour codes are used. On Windows versions that lack support for this you can use the ANSICON terminal. Executables can be obtained here.

-Dlest_FEATURE_LITERAL_SUFFIX=0
Define this to 1 to append u, l, a combination of these, or f to numeric literals. Default is 0.

-Dlest_FEATURE_REGEX_SEARCH=0
Define this to 1 to enable regular expressions to select tests. Default is 0.

Note: You have to make sure the compiler's library has a working std::regex_search(); not all do currently. GCC 4.8.1's regex search function doesn't work yet. Visual C++ probably has a working regex search function since VC9, Visual Studio 2008 (tested VC10, Visual Studio 2010).

-Dlest_FEATURE_TIME_PRECISION=0
Define this to set the precision of the duration in ms reported with option --time. Default is 0.

-Dlest_FEATURE_WSTRING=1
Define this to 0 to remove references to std::wstring. Default is 1.

-Dlest_FEATURE_RTTI (undefined)
lest tries to determine if RTTI is available itself. If that doesn't work out, define this to 1 or 0 to include or remove uses of RTTI (currently a single occurrence of typeid used for reporting a type name). Default is undefined.

Standard selection macro

-Dlest_CPLUSPLUS=199711L
Define this macro to override the auto-detection of the supported C++ standard, or if your compiler does not set the __cplusplus macro correctly.

Namespace

namespace lest { }
Types and functions are located in namespace lest.

Tests

struct env { };

struct test
{
โ€ƒstd::string name;
โ€ƒstd::function<void( env & )> behaviour;
};

You'll need type env and variable lest_env when you have a test case that calls a user-defined function or lambda that uses lest assertions like EXPECT() โ€“ Call reusable function, Call reusable templated function, and Call reusable lambda.

Main

A typical main() function for lest may look as follows:

#include "lest/lest.hpp"

const lest::test specification[] = { CASE("..."){} };

int main( int argc, char *argv[] )
{
    if ( int failures = lest::run( specification, argc, argv ) )
        return failures;
    
    return std::cout << "All tests passed\n", EXIT_SUCCESS;
}

Compile and run:

prompt>g++ -std=c++11 -o main.exe -I../include main.cpp && main.exe
All tests passed

Or, if feedback on success is moved to the command line:

#include "lest/lest.hpp"

const lest::test specification[] = { CASE("..."){} };

int main( int argc, char *argv[] )
{
    return lest::run( specification, argc, argv );
}

Compile and run with feedback on success:

prompt>g++ -std=c++11 -o main.exe -I../include main.cpp && main.exe && echo All tests passed
All tests passed

You can use the following variants of lest's run() function in main.

inline
int run( std::vector<test> specification, std::vector<std::string> arguments, std::ostream & os = std::cout );

inline
int run( std::vector<test> specification, int argc, char * argv[], std::ostream & os = std::cout );

template<std::size_t N>
int run( test const (& specification )[N], std::ostream & os = std::cout );

template<std::size_t N>
int run( test const (& specification )[N], std::vector<std::string> arguments, std::ostream & os = std::cout );

template<std::size_t N>
int run( test const (& specification )[N], int argc, char * argv[], std::ostream & os = std::cout );

  • specification - collection of tests
  • arguments - options and arguments to select and omit tests
  • argc, arcv - options and arguments to select and omit tests
  • os - stream to report to
  • returns number of failing tests

Main (Trompeloeil)

You can integrate the Trompeloeil mocking framework with lest by providing a reporter for Trompeloeil โ€“ Code example.

#include "lest/lest.hpp"
#include "trompeloeil.hpp"

int main( int argc, char * argv[] )
{
    std::ostream & stream = std::cout;
    
    trompeloeil::set_reporter(
        [&stream]( trompeloeil::severity severity, const char * file, unsigned long line, std::string const & msg )
    {
        if ( severity == trompeloeil::severity::fatal )
        {
            throw lest::message{"", lest::location{ line ? file : "[file/line unavailable]", int(line) }, "", msg };
        }
        else
        {   
            stream << lest::location{ line ? file : "[file/line unavailable]", int(line) } << ": " << msg;
        }
    });

    return lest::run( specification, argc, argv, stream );
}

Floating point comparison

lest provides class approx to compare floating point values โ€“ Code example.

class approx { };

Use approx as follows:

EXPECT( 1.23 == approx( 1.23 ) );
EXPECT( 1.23 != approx( 1.24 ) );

EXPECT( 1.23 != approx( 1.231 ) );
EXPECT( 1.23 == approx( 1.231 ).epsilon( 0.1 ) );

approx custom = approx::custom().epsilon( 0.1 );

EXPECT( approx( 1.231 ) != 1.23 );
EXPECT( custom( 1.231 ) == 1.23 );

Class approx also provides less-than or equal and greater-than or equal operators.

Reporting a user-defined type

lest allows you to report a user-defined type via operator<<() โ€“ Code example.

To report a type not yet supported by lest, define a streaming function for it:

namespace ns {
โ€ƒstruct user-defined-type { ... };
โ€ƒstd::ostream & operator<< ( std::ostream & os, user-defined-type const & type )
โ€ƒ{
โ€ƒโ€ƒusing lest::to_string;
โ€ƒโ€ƒreturn os << ... ;
โ€ƒ}
}

In it, stream the constituent parts of the type via lest's to_string() conversion functions.

Variants of lest

Various variants of lest are kept here. The simple ones, such as lest_basic and lest_decompose provide an easy read into the techniques used and remain the tiny test frameworks that are a good fit to include with small projects.

You are encouraged to take it from here and change and expand it as you see fit and publish your variant. If you do, I'd much appreciate to hear from you!

  • lest.hpp - lest's latest development, this project.
  • lest_basic.hpp - lest at its very basic, this project.
  • lest_decompose.hpp - lest with expression decomposition, this project.
  • lest_cpp03.hpp - lest with expression decomposition for C++03, this project.
  • hamlest - matchers for lest.
  • lest with groups - Pavel Medvedev

Features of lest

Feature / variant latest cpp03 decompose basic
Assert expressions โœ“ โœ“ โœ“ โœ“
Assert exceptions โœ“ โœ“ โœ“ โœ“
Assert abortion (death) contrib contrib - -
Assert assertions (death) contrib contrib - -
Expression decomposition โœ“ modest modest -
Literal suffix u, l, f โœ“ - - -
Colourised output โœ“ โœ“ - -
BDD style scenarios โœ“ โœ“ - -
Fixtures (sections) โœ“ โœ“ - -
Floating point comparison, approx โœ“ โœ“ - -
Floating point comparison, ulp - - - -
Test selection (include/omit) โœ“ โœ“ - -
Test selection (regexp) โœ“ โœ“ - -
Help screen โœ“ โœ“ - -
Abort at first failure โœ“ โœ“ - -
Count selected tests โœ“ โœ“ - -
List tags of selected tests โœ“ โœ“ - -
List selected tests โœ“ โœ“ - -
Report passing tests โœ“ โœ“ - -
Time duration of tests โœ“ โœ“ - -
Control order of tests โœ“ โœ“ - -
Repeat tests โœ“ โœ“ - -
Auto registration of tests โœ“ โœ“ - -
Modules of tests โœ“ โœ“ - -
ย  ย  ย  ย  ย 
Suites of tests - - - -
Value-parameterised tests - - - -
Type-parameterised tests - - - -
Test data generators - - - -
Hamcrest matchers +/- - - -
Mocking support - - - -
Logging facility - - - -
Break into debugger - - - -
Concurrent execution of tests - - - -
Isolated execution of tests - - - -

Reported to work with

The table below mentions the lowest version of a compiler lest is reported to work with.

Variant / compiler clang GCC VC
lest (latest) 3.2 4.8.1 12
lest_basic 3.2 4.6 12
lest_decompose 3.2 4.6 12
lest_cpp03 (decompose) ? ? 8

Note: I've made a few concessions to enable compilation of lest.hpp with Visual C++:

  • Prevent error C2797: replace braced member initialisation with C++98 style initialisation.
  • Prevent error C2144: use enum{ value } instead of static constexpr bool in struct is_container (for VC only).

Building tests and examples

Tests and examples can be build with Buck, via Makefiles or by using CMake.

To build the tests and examples as described below you need:

The following steps assume that the lest source code has been cloned into a directory named lest.

Buck

lest> buck run test:test_lest_basic
lest> buck run test:test_lest_cpp03
lest> buck run test:test_lest_decompose
lest> buck run test:test_lest

CMake

  1. Create a directory for the build outputs for a particular architecture.
    Here we use lest/build.

     lest> mkdir build && cd build
    
  2. Configure CMake to use the compiler of your choice (run cmake --help for a list) and build the tests for lest and the examples.

     lest/build> cmake -G "Unix Makefiles" [see 3. below] ..
    
  3. Optional. You can control above configuration through the following options:

    • -DLEST_BUILD_TEST=ON: build the tests for lest, default on
    • -DLEST_BUILD_EXAMPLE=ON: build the examples, default on
    • -DLEST_BUILD_CONTRIB=OFF: build the contrib folder, default off
    • -DLEST_EXTRA_WARNINGS=OFF: build with extra warnings and warnings as errors, default off
  4. Build the test suite.

     lest/build> cmake --build .
    
  5. Run the test suite.

     lest/build> ctest -V
    

All tests should pass, indicating your platform is supported and you are ready to use lest. Note that quite some examples fail. They do so to demonstrate the usage of things.

Contributions to lest

Folder contrib contains extensions to lest. These extensions are not part of lest itself because for example they use non-standard behaviour, they are considered to be for a too-specific use case, or they are considered not yet ripe for inclusion in lest and we first like to gain more experience with them.

Other test frameworks

This comparison of Catch, doctest and lest in table form may help you to discover similarities and differences of these frameworks.

Notes and references

[1] Kevlin Henney on Rethinking Unit Testing in C++ (Video).

[2] Martin Moene. Elefant C++11 test setup on the ACCU mailing list accu-general (requires login). It mentions the C++11 test appoach Andrzej Krzemieล„ski uses for Optional. A library for optional (nullable) objects for C++11.

[3] Phil Nash. CATCH, an automated test framework for C, C++ and Objective-C.

[4] A more technically informed name: lest - lambda engaged small tester.

More Repositories

1

span-lite

span lite - A C++20-like span for C++98, C++11 and later in a single-file header-only library
C++
495
star
2

optional-lite

optional lite - A C++17-like optional, a nullable object for C++98, C++11 and later in a single-file header-only library
C++
393
star
3

string-view-lite

string_view lite - A C++17-like string_view for C++98, C++11 and later in a single-file header-only library
C++
364
star
4

expected-lite

expected lite - Expected objects in C++11 and later in a single-file header-only library
C++
274
star
5

variant-lite

variant lite - A C++17-like variant, a type-safe union for C++98, C++11 and later in a single-file header-only library
C++
230
star
6

ring-span-lite

ring-span lite - A C++yy-like ring_span type for C++98, C++11 and later in a single-file header-only library
C++
136
star
7

any-lite

any lite - A C++17-like any, a type-safe container for single values of any type for C++98, C++11 and later in a single-file header-only library
C++
123
star
8

PhysUnits-CT-Cpp11

A small C++11, C++14 header-only library for compile-time dimensional analysis and unit/quantity manipulation and conversion
C++
77
star
9

nonstd-lite

Parent of *-lite repositories, a migration path to post-C++11 features for pre-C++11 environments
Batchfile
68
star
10

clue

A tiny single-file header-only C++ logging framework
C++
56
star
11

jthread-lite

C++20's jthread for C++11 and later in a single-file header-only library
C++
53
star
12

expected-dark

Expected objects for C++11 and later (and later perhaps C++98 )
C++
52
star
13

byte-lite

byte lite - A C++17-like byte type for C++98, C++11 and later in a single-file header-only library
C++
51
star
14

value-ptr-lite

value-ptr-lite - A C++ smart-pointer with value semantics for C++98, C++11 and later in a single-file header-only library
C++
44
star
15

observer-ptr-lite

observer-ptr - An observer_ptr for C++98 and later in a single-file header-only library (Extensions for Library Fundamentals, v2, v3)
C++
36
star
16

scope-lite

A migration path to C++ library extensions scope_exit, scope_fail, scope_success, unique_resource
C++
34
star
17

type-lite

type - Strong types for C++98, C++11 and later in a single-file header-only library
C++
34
star
18

bit-lite

bit-lite - C++20 bit operations for C++98 and later in a single-file header-only library
C++
33
star
19

martin-moene.blogspot.com

Code from my Blog
C++
30
star
20

invoke-lite

A single-file header-only version of C++17-like invoke() for C++98, C++11 and later
C++
29
star
21

catch-lest-other-comparison

Tabularised feature comparison between Catch, doctest and lest C++ test frameworks
23
star
22

kalman-estimator

kalman-estimator - a Kalman estimator in C++
C++
22
star
23

string-lite

String facilities for C++98 and later - a library in search of its identity.
C++
19
star
24

optional-bare

optional bare - A simple version of a C++17-like optional for default-constructible, copyable types, for C++98 and later in a single-file header-only library
C++
18
star
25

spike-expected

expected: kind of optional?
HTML
16
star
26

status-value-lite

status-value - A class for status and optional value for C++11 and later, C++98 variant provided in a single-file header-only library
C++
14
star
27

atomic-lite

atomic lite - a C++11 atomic operations library for C++98 and later
C++
12
star
28

PhysUnits-RT

A C++ header-only library for run-time dimensional analysis and unit/quantity manipulation and conversion
C++
11
star
29

PhysUnits-CT

A C++ header-only library for compile-time dimensional analysis and unit/quantity manipulation and conversion
C++
11
star
30

WholeValue

Whole value idiom made easy in C++
C++
8
star
31

optional-fun-lite

optional-fun lite - Functional algorithms for optional (lite) for C++98, C++11 and later in a single-file header-only library
C++
6
star
32

EngFormat-Cpp

C++ based Engineering Notation Formatter
C++
6
star
33

indirect-value-lite

indirect_value lite โ€“ An indirect value-type for C++11 and later in a single-file header-only library (p1950)
C++
6
star
34

ACCUConf2016

Slides and other materials from ACCU Conference 2016
5
star
35

array_view2d

A simple 2D view on an array or vector for C++98 and C++11
C++
5
star
36

clamp

Limit a value or a range of values to fall between two extremes
C++
4
star
37

boolean-lite

boolean lite: A strong boolean type for C++98 and later
C++
4
star
38

svn-churn

A simple Python script to determine file churn and fix count of a Subversion repository.
Python
3
star
39

active-lite

Active objects
CMake
2
star
40

clamp-proposal

Propose clamp for C++ standard
CSS
2
star
41

kalman-estimator-ada

kalman-estimator - a Kalman estimator in Ada, sibling of Kalman estimator in C++
Makefile
2
star
42

nonstd-lite-project

Stuff common to nonstd lite repositories
CMake
2
star
43

Test-Normalised

1
star
44

bibliography

My bookshelf
TeX
1
star
45

hamlest

Matchers for lest
C++
1
star
46

wordindex

wordindex: create a linenumber cross-referenced list of words
C++
1
star
47

Test-NonNormalised

1
star
48

wordindex-ranged

wordindex: create a linenumber cross-referenced list of words using C++20 std::ranges
C++
1
star
49

testdox

TestDox - generate a readable overview of test case names from the specified files.
Hack
1
star