Home

Awesome

jest (C++14 unit test framework)

jest is a sane and minimal (header only) C++ unit test framework that uses a template-based approach to registering tests. Absolutely no macros are used or needed for test writing and the whole API can be described in the following example:

#include <jest/jest.hpp>

/* Step 1: Define a group type and object. */
struct ex_1{ };
using ex_1_group = jest::group<ex_1>;
ex_1_group const ex_1_obj{ "example" };

/* Step 2: Specialize for your group and all tests you'd like. */
namespace jest
{
  template <> template <>
  void ex_1_group::test<0>() /* Tests are numbered; the order does not matter. */
  {
    int const i{};
    float const f{ 3.14 };
    expect(i == f);
  }
  template <> template <> /* Double template bit here is required. */
  void ex_1_group::test<1>()
  {
    int const i{};
    float const f{ 3.14 };
    float const f2{ f * 2.0f };
    expect_equal(i, f, f2); /* Variadic; compares all permutations of pairs. */
  }
  template <> template <>
  void ex_1_group::test<2>()
  { fail("woops"); }
  template <> template <>
  void ex_1_group::test<3>()
  { fail(); }
  template <> template <>
  void ex_1_group::test<4>()
  { expect_equal(0, 0.0f, 0x00, 0000, 0b000); }
  template <> template <>
  void ex_1_group::test<5>()
  { expect_almost_equal(3.140000f, 3.140001f); } /* Using combined tolerance. */
  template <> template <>
  void ex_1_group::test<28>() /* Test numbers do not need to be sequential. */
  { expect_equal("string", "String"); }
  template <> template <>
  void ex_1_group::test<29>()
  {
    expect_exception<std::runtime_error>([] /* Variadic; any number of exception types. */
    { throw std::runtime_error{""}; });
  }
}

/* Step 3: Create a worker which will run the tests. */
int main()
{
  jest::worker const j{};
  return j();
}

Possible output:

running group 'example'
  test 0 failure: failed 'unexpected' (false)
  test 1 failure: failed 'not equal' (0, 3.14)
  test 2 failure: failed 'explicit fail' ("woops")
  test 3 failure: failed 'explicit fail' ("")
  test 4 success
  test 28 failure: failed 'not equal' ("string", "String")
  test 29 success
finished group 'example'

5/7 test(s) failed

What the hell is template <> template <>?

You're specializing a member function of jest::group, which is parameterized on your test type. It also inherits from your test type, giving direct access to your test type's member variables from within jest::group::test.

An example of where I use this group-specific data is for testing the output of certain functions to stdout. Add a std::stringstream to the test data, redirect std::cout to it, and now you can check its contents for each test. Example:

#include <jest/jest.hpp>

#include <iterator>
#include <algorithm>

/* My test type is output, which has a stringstream. */
struct output
{
  output()
  { std::cout.rdbuf(out.rdbuf()); }

  std::stringstream out; /* This will be accessible in each test. */
};
using output_group = jest::group<output>;
output_group const output_obj{ "output" };

namespace jest
{
  namespace detail
  {
    void speak()
    { std::cout << "BARK"; }
    void yell(std::string const &s)
    {
      std::transform(std::begin(s), std::end(s),
                     std::ostream_iterator<char>(std::cout),
      [](char const c)
      { return std::toupper(c); });
    }
  }

  template <> template <>
  void output_group::test<0>()
  {
    /* Here, I can access `out`, a member variable of my test type. */
    detail::speak();
    expect_equal(out.str(), "BARK");
    out.str("");
  }
  template <> template <>
  void output_group::test<1>()
  {
    detail::yell("testing is the best");
    expect_equal(out.str(), "TESTING IS THE BEST");
    out.str("");
  }
}

int main()
{
  jest::worker const j{};
  return j();
}

Possible output:

running group 'output'
  test 0 success
  test 1 success
finished group 'output'

all 2 tests passed

Installation

Since jest is a header-only library, simply copy over the contents of include to your project, or, better yet, add jest as a submodule and introduce jest/include to your header search paths.

Full installation can also be achieved by using ./configure && make install. See the configure script for prefix options.