Coder Social home page Coder Social logo

gunit's Introduction

Boost Licence Build Status Github Issues Join the chat at https://gitter.im/cpp-testing/GUnit


Testing

"If you liked it then you should have put a test on it", Beyonce rule

GUnit

GoogleTest/GoogleMock/Cucumber on steroids

If you like/and or are struck with GoogleTest/GoogleMock/Cucmber-cpp on clang/gcc with C++14 you may want to consider using GUnit in order to improve your testing experience by:

  • No more hand written mocks! (No more MOCK_CONST_METHODX)
  • No more base classes (SetUp/TearDown) and label as identifiers for GoogleTest (GTEST/SHOULD)
  • No more need to instansiate and maintain mocks by hand (GMake - Automatic mocks injection)
  • No more need for another tool (cucumber) and ruby to run BDD scenarios (Gherkin - Given/When/Then)

Motivation for GUnit

Example (TDD)

GoogleTest/GoogleMock                           | GUnit
------------------------------------------------+---------------------------------------------
#include <gmock/gmock.h>                        | #include <GUnit.h> // one header
#include <gtest/gtest.h>                        |
                                                |
class mock_i1 : public i1 {                     | // mock_i1 is NOT NEEDED!
public:                                         |
 MOCK_CONST_METHOD1(f1, bool(int));             |
};                                              |
                                                |
class mock_i2 : public i2 {                     | // mock_i2 is NOT NEEDED!
public:                                         |
 MOCK_METHOD0(f2_1, void());                    |
 MOCK_METHOD0(f2_2, void());                    |
 MOCK_METHOD0(f2_3, void());                    |
 MOCK_METHOD0(f2_4, void());                    |
};                                              |
                                                |
class mock_i1 : public i1 {                     | // mock_i3 is NOT NEEDED!
public:                                         |
 MOCK_METHOD3(f3, void(int, int, int));         |
};                                              |
struct ExampleTest : testing::Test {            |
 void SetUp() override {                        |
   sut = std::make_unique<example>(m1, m2, m3); | // SetUp IS OPTIONAL!
 }                                              |
                                                |
 mock_i1 m1;                                    | // members
 mock_i2 m2;                                    | // for
 mock_i3 m3;                                    | // testing
 std::unique_ptr<example> sut;                  | // are NOT NEEDED!
};                                              |
TEST_F(ExampleTest, ShouldCallF1) {             |GTEST(example) { // set-up
 using namespace testing;                       | using namespace testing;
                                                |
 EXPECT_CALL(m1,f1(_)).WillOnce(Return(true));  | SHOULD("call f1") {
 EXPECT_CALL(m2,f2_1()).Times(1);               |  EXPECT_CALL(mock<i1>(),(f1)(_)).WillOnce(Return(true));
 EXPECT_CALL(m3,f3(0, 1, 2)).Times(1);          |  EXPECT_CALL(mock<i2>(),(f2_1)()).Times(1);
                                                |  EXPECT_CALL(mock<i3>(),(f3)(0, 1, 2)).Times(1);
 sut->test();                                   |
}                                               |  sut->test(); // sut and mocks were
                                                | }             // created automatically
TEST_F(ExampleTest, ShouldCallF2) {             |
 using namespace testing;                       | SHOULD("call f2") {
                                                |  EXPECT_CALL(mock<i1>(),(f1)(_)).WillOnce(Return(false));
 EXPECT_CALL(m1,f1(_)).WillOnce(Return(false)); |  EXPECT_CALL(mock<i2>(),(f2_2)()).Times(1);
 EXPECT_CALL(m2,f2_2()).Times(1);               |  EXPECT_CALL(mock<i3>(),(f3)(0, 1, 2)).Times(1);
 EXPECT_CALL(m3,f3(0, 1, 2)).Times(1);          |
                                                |  sut->test();
 sut->test();                                   | } // tear-down
}                                               |}

Example (BDD)

  • test/Features/Calc/addition.feature (Gherkin)
Feature: Calc Addition
  In order to avoid silly mistakes
  As a math idiot
  I want to be told the sum of two numbers

  Scenario: Add two numbers
    Given I have entered 20 into the calculator
      And I have entered 30 into the calculator
     When I press add
     Then the result should be 50 on the screen

Steps Implementation

STEPS("Calc*") = [](auto steps) {
  Calculator calc{};
  double result{};

  steps.Given("I have entered {n} into the calculator") =
    [&](double n) {
      calc.push(n);
    };

  steps.When("I press add") =
    [&] {
      result = calc.add();
    };

  steps.When("I press divide") =
    [&] {
      result = calc.divide();
    };

  steps.Then("the result should be {expected} on the screen") =
    [&] (double expected) {
      EXPECT_EQ(expected, result);
    };

  return steps;
};

Usage

SCENARIO="test/Features/Calc/addition.feature" ./test --gtest_filter="Calc Addition.Add two numbers"

Output

[==========] Running 1 test from 1 test case.
[----------] Global test environment set-up.
[----------] 1 tests from Calc Addition
[ RUN      ] Calc Addition.Add two numbers
[    Given ] I have entered 20 into the calculator            # CalcSteps.cpp:12
[    Given ] I have entered 30 into the calculator            # CalcSteps.cpp:14
[     When ] I press add                                      # CalcSteps.cpp:16
[     Then ] the result should be 50 on the screen            # CalcSteps.cpp:19
[----------] Global test environment tear-down
[==========] 1 test from 1 test case ran. (7 ms total)
[  PASSED  ] 1 tests.

GUnit

  • Header only library (BDD/Gherkin support requires linking with libgherkin-cpp)
  • Based on top of GoogleTest/GoogleMock
  • Modular (GMock/GMake/GTest/GTest-Lite are independent)
    • GUnit.GMock - GoogleMock without hand written mocks
      • No more hand written mocks!
      • Support for more than 10 parameters
      • Quicker compilation times
      • Support for unique_ptr without any tricks
      • Support for overloaded operators
      • Support for mocking classes with constructors
      • 100% Compatible with Google Mocks
    • GUnit.GMake - Makes creation of System Under Test (SUT) and Mocks easier
      • No need to instantiate System Under Test and Mocks
        • Automatic mocks injection
    • GUnit.GTest - GooglTest with strings and more friendly macros
      • Test cases with string as names
      • No more SetUp/TearDown (SHOULD clauses)
      • One (GTEST) macro for all types of tests
      • 100% Compatible with tests using GTest
    • GUnit.GTest-Lite - lightweight, limited, no-macro way of defining simple tests
    • GUnit.GScenario - Gherkin/Cucumber implementation for C++ without Ruby dependencies
  • Requirements
  • Quick start
    $mkdir build && cd build && cmake ..
    $make && ctest

Why GUnit it's based on GoogleTest/GoogleMock?

  • GoogleTest

  • GoogleMock

    • (+) Widely used
    • (+) Stable
    • (+) Powerful
    • (+) Well documented
    • (-) Hand written mocks
      • Who likes writing and maintaining these?
      class MockInterface : public interface {
      public:
        MOCK_CONST_METHOD1(get, bool());
        MOCK_METHOD1(set, void(bool));
      };
    • (-) Macro based

    Towards Painless Testing with GoogleTest and GoogleMock. 15

  • (-) Slow to compile

GUnit.GMock

  • GoogleMock without writing and maintaining mocks by hand
  • Supported features
    • EXPECT_CALL (requires additional parens for function call)
    EXPECT_CALL(mock, function(42)).WillOnce(Return(true)); // GoogleMock
    EXPECT_CALL(mock, (function)(42)).WillOnce(Return(true)); // GUnit.GMock
    EXPECT_INVOKE(mock, function, 42) // GUnit.GMock
    • ON_CALL (requires additional parens for function call)
    ON_CALL(mock, function()).WillByDefault(Return(true)); // GoogleMock
    ON_CALL(mock, (function)()).WillByDefault(Return(true)); // GUnit.GMock
    • Return/ReturnRef
    • WaggyMock/StrictMock/NiceMock
    • Compile error when parameters and expectations don't match
    • It works together with traditional GoogleMock mocks (See Example)
  • Synopsis
    namespace testing {
      template <class T>
      class GMock {
        static_assert(std::is_abstract<T>::value, "T has to be an abstract type");
        static_assert(std::has_virtual_destructor<T>::value, "T has to have a virtual destructor");
    
        GMock() = default;
        GMock(GMock &&) = default;
        GMock(const GMock &) = delete;
    
      public:
        using type = T;
    
        T&() object();
        const T&() object() const;
    
        explicit operator T&();
        explicit operator const T&() const;
      };
    
      template <class T>
      using NaggyGMock = GMock<T>;
    
      template <class T>
      using StrictGMock = StrictMock<GMock<T>>;
    
      template <class T>
      using NiceGMock = NiceMock<GMock<T>>;
    
      /**
       * [Proposal - generic factories]
       *   http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0338r0.pdf
       *
       * @tparam T type to be created ex. std::unique_ptr<example>
       * @param args arguments (converts GMock for given types T*, T&, shared_ptr, unique_ptr)
       * @return T instance
       */
      template <class T, class... TArgs>
      auto make(TArgs&&... args);
    } // testing
    
    template<class TMock>
    auto object(TMock&); // converts mock to the underlying type

GUnit.GMock - Tutorial by example

class iconfig {
 public:
  virtual bool is_dumpable() const = 0;
  virtual ~iconfig() = default;
};
class iprinter {
 public:
  virtual ~iprinter() = default;
  virtual void print(const std::string& text) = 0;
};
class example {
 public:
  example(const iconfig& config, const std::shraed_ptr<iprinter>& printer)
    : config(config), printer(printer)
  { }

  void update() {
    if (config.is_dumpable()) {
      printer->print("text");
    }
  }

 private:
  const iconfig& config;
  std::shared_ptr<iprinter> printer;
};

Test (V1)

TEST(Test, ShouldPrintTextWhenUpdate) {
  using namespace testing;
  GMock<iconfig> mockconfig; // defines and creates a mock
  auto mockprinter = std::make_shared<GMock<iprinter>>(); // defines and creates a mock

  example sut{static_cast<const iconfig&>(mockconfig)
            , object(mockprinter)};

  EXPECT_CALL(mockconfig, (is_dumpable)()).WillOnce(Return(true)); // additional parens
  EXPECT_CALL(*mockprinter, (print)("text")); // additional parens

  sut.update();
}
  • (+) NO HAND WRITTEN MOCKS
  • (-) Additional casting of mocks is required
  • (~) Additional parens with a method call

Can we do better?

Test (V2) / using make

TEST(Test, ShouldPrintTextWhenUpdate) {
  using namespace testing;
  StrictGMock<iconfig> mockconfig; // strict mock
  auto mockprinter = std::make_shared<StrictGMock<iprinter>>(); // strict mock

  auto sut = make<example>(mockconfig, mockprinter); // automatically converts mocks to interfaces

  EXPECT_CALL(mockconfig, (is_dumpable)()).WillOnce(Return(true));
  EXPECT_CALL(*mockprinter, (print)("text"));

  sut.update();
}
  • (+) No castings required

Is the make call generic?

Test (V2.1) / using make and unique_ptr

TEST(Test, ShouldPrintTextWhenUpdate) {
  using namespace testing;
  StrictGMock<iconfig> mockconfig;
  auto mockprinter = std::make_shared<StrictGMock<iprinter>>();

  // create a unique_ptr
  auto sut = make<std::unique_ptr<example>>(mockconfig, mockprinter);

  EXPECT_CALL(mockconfig, (is_dumpable)()).WillOnce(Return(true));
  EXPECT_CALL(*mockprinter, (print)("text"));

  sut->update();
}

GMock conversion to the underlying type

foo_ref(IFoo&);
foo_ptr(IFoo*);

int main() {
  GMock<IFoo> mock;
  foo_ref(object(mock)); // converts mock to IFoo&
  foo_ptr(object(mock)); // converts mock to IFoo*
};
foo_up(std::unique_ptr<IFoo>);
foo_ref(IFoo&);
foo_ptr(IFoo*);

int main() {
  std::unique_ptr<StrictGMock<IFoo>> mock
    = std::make_unique<StrictGMock<IFoo>>();

  foo_up(object(mock));  // converts mock to std::unique_ptr<IFoo>
  foo_ref(object(mock)); // converts mock to IFoo&
  foo_ptr(object(mock)); // converts mock to IFoo*
}
foo_up(std::shared_ptr<IFoo>);
foo_ref(IFoo&);
foo_ptr(IFoo*);

int main() {
  std::shared_ptr<StrictGMock<IFoo>> mock
    = std::make_shared<StrictGMock<IFoo>>();

  foo_sp(object(mock));  // converts mock to std::shared_ptr<IFoo>
  foo_ref(object(mock)); // converts mock to IFoo&
  foo_ptr(object(mock)); // converts mock to IFoo*
}

How to mock overloaded methods?

class interface {
 public:
  virtual void f(int) = 0;
  virtual void f(int) const = 0;
  virtual ~interface() = default;
};

GMock<interface> mock;

EXPECT_CALL(mock, (f, void(int) const)(1));
EXPECT_CALL(mock, (f, void(int))(2));

static_cast<const interface&>(mock).f(1);
mock.object().f(2);

Universal EXPECT_* syntax (works with Google Mock's and GUnit.GMock's)

struct IFoo {
  virtual ~IFoo() noexcept = default;
  virtual bool foo(int) = 0;
};
GMock<IFoo> mock;
EXPECT_INVOKE(mock, foo, 42).WillOnce(Return(42));
// same as EXPECT_CALL(mock, (foo)(42)).WillOnce(Return(42));

Mocking templates?

  • Simple, just put an interface on it!

Example

struct Generic {
  template<class... Ts>
  void foo(Ts...) const;
};

template<class T>
class GenericExample {
public:
  explicit GenericExample(const T&);
  void bar() {
    t.foo(42, 77.0); // call via templated object
  }

private:
  const T& t;
};

/**
 * Needed for testing but it's still better than MOCK_CONST_METHOD2
 */
struct IGeneric {
  virtual ~IGeneric() = 0;
  virtual void foo(int, double) const = 0;
};

StrictGMock<IGeneric> generic{};
GenericExample<IGeneric> sut{object(generic)};

EXPECT_CALL(generic, (foo)(42, 77.0));

sut.bar();

[Advanced] Constructors with non-interface parameters and make (Assisted Injection)

  example(iconfig& config, int value, const std::shared_ptr<iprinter>& printer, int data);
                            ^                                                   ^
                            \_____________________       _______________________/
                                                  \     /
  std::tie(sut, mocks) = make<example, StrictMock>(42, 77); // order of the same types is important
                                                            // but it's not imortant for unique types

[Advanced] Generic Factories

template <class T, class... TArgs>
struct IFactory {
  virtual T create(TArgs...) = 0;
  virtual ~IFactory() = default;
};
using IConfigFactory = IFactory<IConfig, std::string>;
GMock<IConfig> mockconfig;
EXPECT_CALL(mock<IConfigFactory>(), (create)("string")).WillOnce(Return(mockconfig));
  • (+) No specfic factory mocks for given number of parmaeters
  • (+) Factory aliases can be used to determine the mock

GUnit.GMake

  • Removes boilerplate mocks declaration
  • Creates System Under Test (SUT) the same way despite the constructor changes
  • Synopsis
    namespace testing {
      /**
       * [Proposal - generic factories]
       *   http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0338r0.pdf
       *
       * @tparam T type to be created ex. std::unique_ptr<example>
       * @tparam TMock mock type
       *          NaggyMock  - warning (default)
       *          StrictMock - error
       *          NiceMock   - ignore
       *
       * @tparam TMocks specific mocks different than defaulted by TMock
       *          ex. StrictGMock<interface>
       *
       * @param args arguments (converts GMock for given types T*, T&, shared_ptr, unique_ptr)
       * @return pair{T, mocks}
       */
      template <class T, template <class> class TMock, class... TMocks, class... TArgs>
      auto make(TArgs&&... args);
    
      template <class T>
      class GTest : public Test {
      public:
        using SUT = std::unique_ptr<T>;
    
      protected:
        template <class TMock>
        decltype(auto) mock();
    
        SUT sut;
        mocks_t mocks;
      };
    } // testing

GUnit.GMake - Tutorial by example

Test (V3 - C++17)

TEST(Test, ShouldPrintTextWhenUpdate) {
  using namespace testing;
  auto [sut, mocks] = make<example, NaggyMock>(); // create NaggyMocks when required

  EXPECT_CALL(mocks.mock<iconfig>(), (is_dumpable)()).WillOnce(Return(true));
  EXPECT_CALL(mocks.mock<iprinter>(), (print)("text"));

  sut.update();
}
  • (+) Required mocks are created automatically

  • (+) example constructor might be refactored without changing test cases!

    make<example, NaggyMock>() can create...
      example(const std::shared_ptr<iprinter>& printer, const iconfig& config);
      example(const iconfig& config, iprinter& printer);
      example(iconfig* config, iprinter* printer);
      ...

Test (V3 - C++14)

TEST(Test, ShouldPrintTextWhenUpdate) {
  using namespace testing;
  std::unique_ptr<example> sut;
  mocks_t mocks;
  std::tie(sut, mocks) = make<std::unique_ptr<example>, StrictMock>(); // create StrictMock when required

  EXPECT_CALL(mocks.mock<iconfig>(), (is_dumpable)()).WillOnce(Return(true));
  EXPECT_CALL(mocks.mock<iprinter>(), (print)("text"));

  sut->update();
}

Let's refactor (remove duplicates) from V3 then!

Test (V4) / using GUnit.GMake

class Test : public testing::Test {
public:
  void SetUp() override {
    std::tie(sut, mocks) =
      testing::make<std::unique_ptr<example>, NaggyMock>();
  }
};
TEST(Test, ShouldPrintTextWhenUpdate) {
  using namespace testing;
  EXPECT_CALL(mock<iconfig>(), (is_dumpable)()).WillOnce(Return(true));
  EXPECT_CALL(mock<iprinter>(), (print)("text"));

  sut->update();
}
  • (+) No repetitions with more than 1 test!

GUnit.GTest

  • Simplifies usage of GoogleTest (no more label as test case names!)

  • Synopsis

      #define GTEST(type_to_be_tested OR test_case_name,
                    [optional] additional_test_case_name,
                    [optional] parametric test values);
      #define DISABLED_GTEST(...); // disable test
    
      #define SHOULD(test_case_name); creates a new test case inside GTEST
      #define DISABLED_SHOULD(test_case_name); // disable should clause (test case)

GUnit.GTest - Tutorial by example

Simple test

GoogleTest                                      | GUnit
------------------------------------------------+---------------------------------------------
TEST(SimpleTest, ShouldDoNothing)               | GTEST("Should do nothing")
{ }                                             | { }

Simple test with a fixture

GoogleTest                                      | GUnit
------------------------------------------------+---------------------------------------------
TEST(SimpleTest, ShouldDoNothing)               | GTEST("Simple Test", "Should do nothing")
{ }                                             | { }

Test with a base class

GoogleTest                                      | GUnit
------------------------------------------------+---------------------------------------------
struct FooTest : testing::Test { };             | struct FooTest : testing::Test { };
                                                |
TEST_F(FooTest, ShouldDoNothing)                | GTEST(FooTest, "Should do nothing")
{ }                                             | { }

Multiple tests with a base class

GoogleTest                                      | GUnit
------------------------------------------------+---------------------------------------------
struct FooTest : testing::Test { };             | struct FooTest : testing::Test { };
                                                |
TEST_F(FooTest, ShouldDoNothingTest1) { }       | GTEST(FooTest, "Should do nothing test 1") { }
TEST_F(FooTest, ShouldDoNothingTest2) { }       | GTEST(FooTest, "Should do nothing test 2") { }

Test with SUT/Mocks creation

GoogleTest                                      | GUnit
------------------------------------------------+---------------------------------------------
class IFoo;                                     | class IFoo;
class Example;                                  | class Example;
                                                |
TEST(FooTest, ShouldCallFoo) {                  | GTEST(Example, "Should call foo") {
  std::shared_ptr<StrictGMock<IFoo>> fooMock    |   EXPECT_CALL(mock<IFoo>(), (foo)())
   = std::make_shared<StrictGMock<IFoo>>();     |     .WillOnce(Return(42));
                                                |   EXPECT_EQ(42, sut->run());
  std::unique_ptr<Example> sut                  | }
   = std::make_unique<Example>(object(fooMock));|
                                                |
  EXPECT_CALL(*fooMock, (foo)())                |
    .WillOnce(Return(42));                      |
  EXPECT_EQ(42, sut->run());                    |
}                                               |

Multiple tests with SUT and Mocks

GoogleTest                                      | GUnit
------------------------------------------------+---------------------------------------------
class IFoo;                                     | class IFoo;
class Example;                                  | class Example;
                                                |
struct FooTest : testing::Test {                | GTEST(Example) {
  std::shared_ptr<StrictGMock<IFoo>> fooMock    |   std::cout << "set up" << '\n';
   = std::make_shared<StrictGMock<IFoo>>();     |
  std::unique_ptr<Example> sut                  |   SHOULD("call foo") {
   = std::make_unique<Example>(object(fooMock));|     EXPECT_CALL(mock<IFoo>(), (foo)())
                                                |       .WillOnce(Return(42));
  void SetUp() override {                       |     EXPECT_EQ(42, sut->run());
    std::cout << "set up" << '\n';              |   }
  }                                             |
                                                |   SHOULD("call foo and return 0") {
  void TearDown() override {                    |     EXPECT_CALL(mock<IFoo>(), (foo)())
    std::cout << "tear down" << '\n';           |       .WillOnce(Return(0));
  }                                             |     EXPECT_EQ(0, sut->run());
};                                              |   }
                                                |
TEST_F(FooTest, ShouldCallFoo) {                |   std::cout << "tear down" << '\n';
  EXPECT_CALL(*fooMock, (foo)())                | }
    .WillOnce(Return(42));                      |
  EXPECT_EQ(42, sut->run());                    | // There are 2 tests cases here!
}                                               | //   1.	Example.Should call foo
                                                | //   2. Example.Should call foo and return 0
TEST_F(FooTest, ShouldCallFooAndRet0) {         | //
  EXPECT_CALL(*fooMock, (foo)())                | // SetUp and TeardDown will be called
    .WillOnce(Return(0));                       | // separately for both of them
  EXPECT_EQ(0, sut->run());                     |
}

Disable simple test

GoogleTest                                      | GUnit
------------------------------------------------+---------------------------------------------
TEST(DISABLED_Test, ShouldDoSomething)          | DISABLED_GTEST("Should do something")
{ }                                             | { }

Disable multiple tests

GoogleTest                                      | GUnit
------------------------------------------------+---------------------------------------------
TEST_F(FooTest, DISABLED_ShouldDoA) {}          | DISABLED_GTEST(FooTest) {
TEST_F(FooTest, DISABLED_ShouldDoB) {}          |   SHOULD("Do A") {}
                                                |   SHOULD("Do B") {}
                                                | }

Disable some tests

GoogleTest                                      | GUnit
------------------------------------------------+---------------------------------------------
TEST_F(FooTest, ShouldDoA) {}                   | GTEST(FooTest) {
TEST_F(FooTest, DISABLED_ShouldDoB) {}          |   DISABLED_SHOULD("Do A") {}
```                                             |   SHOULD("Do B") {}
                                                | }

Parametrized tests

GoogleTest                                      | GUnit
------------------------------------------------+---------------------------------------------
class ParamTest :                               | GTEST("ParamTest", "[InstantiationName]",
  public ::testing::TestWithParam<int> { };     |       testing::Values(1, 2, 3)) {
                                                |  SHOULD("be true") { EXPECT_TRUE(GetParam() >= 1; }
TEST_P(ParamTest, ShouldbeTrue) {               |  SHOULD("be false") { EXPECT_FALSE(false); }
  EXPECT_TRUE(GetParam() >= 1);                 | }
}                                               |
                                                |
TEST_P(ParamTest, ShouldBeFalse) {              |
  EXPECT_FALSE(false);                          |
}                                               |
                                                |
INSTANTIATE_TEST_CASE_P(                        |
  InstantiationName,                            |
  ParamTest,                                    |
  testing::Values(1, 2, 3)                      |
);                                              |

Note Running specific should test case requires ':' in the test filter (--gtest_filter="test case pattern:should pattern")

  • --gtest_filter="FooTest*:Do A" # calls FooTest with should("Do A")
  • --gtest_filter="FooTest*:-Do A" # calls FooTest with not should("Do A")
  • --gtest_filter="FooTest*:Do*" # calls FooTest with should("Do...")
  • --gtest_filter="FooTest.:Do*" # calls FooTest with should("Do...")
  • --gtest_filter="-FooTest?:-Do*" # calls not FooTest with not should("Do...")

Example output

[----------] 1 tests from Example
[ RUN      ] Example.Return
[ SHOULD   ] return true
[ SHOULD   ] return false
[       OK ] Example.Return (0 ms)
[----------] 1 tests from Example (0 ms total)

GUnit.GTest-Lite

  • Synopsis
    template <class T, T...>
    constexpr auto operator""_test;
    
    template <class T, T...>
    constexpr auto operator""_test_disabled;

GUnit.GTest-Lite - Tutorial by example

int main() {
  "should always be true"_test = [] {
    EXPECT_TRUE(true);
  };

  "should not be run"_test_disabled = [] {
    EXPECT_TRUE(false);
  };
}

Integration tests with Dependency Injection ([Boost].DI)

class example; // System Under Test

GTEST(example) {
  namespace di = boost::di;

  SHOULD("create example") {
    const auto injector = di::make_injector(
      di::bind<interface>.to(di::NiceGMock{mocks})
    , di::bind<interface2>.to(di::StrictGMock{mocks})
    );

    sut = testing::make<SUT>(injector);

    EXPECT_CALL(mock<interface>(), (get)(_)).WillOnce(Return(123));
    EXPECT_CALL(mock<interface2>(), (f2)(123));

    sut->update();
  }
}

GUnit.GScenario - BDD (Given/When/Then - Gherkin) scenarios

  • Synopsis

    /**
     * @param feature regex expression matching a feature name
     *        example: "Calc*"
     */
    #define STEPS(feature) // register steps for a feature
    
    namespace testing {
      /**
       * Thrown when implementation for given step can't be found
       */
      struct StepIsNotImplemented : std::runtime_error;
    
      /**
       * Thrown when more than one implementation for given step was be found
       */
      struct StepIsAmbiguous : std::runtime_error;
    
      /**
       * Verify whether parameters provided in the step and lambda expression
       * matches at compile-time
       */
      constexpr auto operator""_step();
    
      /**
       * Table parameters from the scenario
       */
      using Table = vector<unordered_map<string_key, string_value>>;
    
      /**
       * Map Gherkin steps (feature file) to implementation
       */
      class Steps {
        /**
         * @param pattern step description (support simple regex)
         *        might be followed by _step to verify parameters at compile time
         * @param table optional table parameter, lambda expression will need a Table parameter
         *
         * Lambda expression has to be assigned
         */
        auto Given(auto pattern, auto table = none);
    
        /**
         * Same as Given but it will show file/line from cpp files instead of feature file
         */
        auto $Given(auto pattern, auto table = none);
    
        auto When(auto pattern, auto table = none);
        auto $When(auto pattern, auto table = none);
    
        auto Then(auto pattern, auto table = none);
        auto $Then(auto pattern, auto table = none);
      };
    } // testing
  • Usage

/**
 * @param args default-constructible types to be injected
 */
STEPS("*") = [](auto steps, args...) {
  // initialize test objects here
  return steps; // has to return steps
};
  • test/Features/Calc/addition.feature
Feature: Addition
  In order to avoid silly mistakes
  As a math idiot
  I want to be told the sum of two numbers

  Scenario Outline: Add two numbers
    Given I have entered <input_1> into the calculator
    And I have entered <input_2> into the calculator
    When I press <button>
    Then the result should be <output> on the screen

  Examples:
    | input_1 | input_2 | button | output |
    | 20      | 30      | add    | 50     |
    | 2       | 5       | add    | 7      |
    | 0       | 40      | add    | 40     |
    | 3       | 222     | add    | 225    |

Steps Implementation

const auto CalcPush = [](auto& calc) {
  return [&](double n) {
    calc.push(n);
  };
};

const auto CalcAdd = [](auto& calc, auto& result) {
  return [&] {
    result = calc.add();
  };
};

const auto CalcDivide = [](auto& calc, auto& result) {
  return [&] {
    result = calc.divide();
  };
};

const auto CalcResult = [](auto& result) {
  return [&](double expected) {
    EXPECT_EQ(expected, result);
  };
};

STEPS("Calc*") = [](auto steps, Calculator calc, double result) {
  steps.Given("I have entered {n} into the calculator"_step)   = CalcPush(calc);
  steps.When ("I press add")                                   = CalcAdd(calc, result);
  steps.When ("I press divide")                                = CalcDivide(calc, result);
  steps.Then ("the result should be {expected} on the screen") = CalcResult(result);
  return steps;
};

Usage

SCENARIO="test/Features/Calc/addition.feature" ./test --gtest_filter="Calc Addition.Add two numbers"

GWT and Mocking?

STEPS("Calc*") = [](auto steps) {
  testing::GMock<IDisplay> display{DEFER_CALLS(IDisplay, show)};
  CalculatorUI calc{testing::object(display)};

  steps.Given("I have entered {n} into the calculator") =
    [&](double n) {
      calc.push(n);
    };

  steps.When("I press add") =
    [&]{ calc.add(); };

  steps.Given("I press divide") =
    [&]{ calc.divide(); };

  steps.Then("the result should be {expected} on the screen") =
    [&] (double expected) {
      EXPECTED_CALL(display, (show)(expected));
    };

  return steps;
};

Note Running specific scenario requires ':' in the test filter (--gtest_filter="feature.scenario")

  • --gtest_filter="Calc Addition.Add two numbers" # calls Calc features test using Addition feature and Add two numbers scenario

Limitations

  • GMock can't mock classes with multiple or virtual inheritance
  • GMock, by default, can fake interface with up to 128 virtual methods

FAQ

Acknowledgements

References

gunit's People

Contributors

kris-jusiak avatar krzysztof-jusiak avatar

Watchers

 avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.