Cut is a simple, practical, and scalable xUnit Test Framework in Modern C++11. It's inspiration comes from the famous testing framework JUnit in Java community.
C/C++ are different languages from most modern ones. Writing tests for them has some very specific challenges. Therefore, simply clone ideas from xUnit frameworks for other languages does not work the best. Programmers have to suffer accidental complexities brought by most of existing frameworks.
Cut is designed for simplifying efforts of programers, in terms of development, maintenance, flexibility of test management, build & run-time strategy, and others.
-
Supported Platform:
- [MAC OS X] supported
- [Linux] supported
- [Windows] partial supported
-
Supported Compilers:
- [CLANG] 3.4 or later.
- [GCC] 4.8 or later.
- [MinGW] ok
- [Cygwin] ok
- [MSVC] not supported.
Cut support 2 intalling method.
- Online Installing
- Manual Installing
$ curl -fsSL https://raw.github.com/ccock/cut/master/install.sh | sh
$ mkdir tmp && cd tmp
$ cmake .. && make
$ sudo make install
$ cd tmp
$ cmake -DENABLE_TEST=on .. && make
$ test/cut-test
$ cmake -G"MinGW Makefiles" ..
$ make
$ make install
$ cmake ..
$ make
$ make install
quantity
├── include
│ └── quantity
├── src
│ └── quantity
└── test
│ ├── main.cpp
└── CMakeLists.txt
#include <cut/cut.hpp>
int main(int argc, char** argv)
{
return cut::run_all_tests(argc, argv);
}
project(quantity)
cmake_minimum_required(VERSION 2.8)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include)
file(GLOB_RECURSE all_files
src/*.cpp
src/*.cc
src/*.c
test/*.cpp
test/*.cc
test/*.c)
add_executable(quantity-test ${all_files})
target_link_libraries(quantity-test cut)
$ mkdir build
$ cd build
$ cmake ..
$ make
$ ./quantity-test
[==========] Running 0 test cases.
[----------] 0 tests from All Tests
[----------] 0 tests from All Tests
[==========] 0 test cases ran.
[ TOTAL ] PASS: 0 FAILURE: 0 ERROR: 0 TIME: 0 us
#include <cut/cut.hpp>
#include <quantity/Length.h>
USING_CUM_NS
FIXTURE(LengthTest)
{
TEST("1 FEET should equal to 12 INCH")
{
ASSERT_THAT(Length(1, FEET), eq(Length(12, INCH)));
}
};
// quantity/Length.h
#include <quantity/Amount.h>
enum LengthUnit
{
INCH = 1,
FEET = 12 * INCH,
};
struct Length
{
Length(Amount amount, LengthUnit unit);
bool operator==(const Length& rhs) const;
bool operator!=(const Length& rhs) const;
private:
const Amount amountInBaseUnit;
};
// quantity/Length.cpp
#include <quantity/Length.h>
Length::Length(Amount amount, LengthUnit unit)
: amountInBaseUnit(unit * amount)
{
}
bool Length::operator==(const Length& rhs) const
{
return amountInBaseUnit == rhs.amountInBaseUnit;
}
bool Length::operator!=(const Length& rhs) const
{
return !(*this == rhs);
}
$ mkdir build
$ cd build
$ cmake ..
$ make
$ ./quantity-test
[==========] Running 1 test cases.
[----------] 1 tests from All Tests
[----------] 1 tests from LengthTest
[ RUN ] LengthTest::1 FEET should equal to 12 INCH
[ OK ] LengthTest::1 FEET should equal to 12 INCH(13 us)
[----------] 1 tests from LengthTest
[----------] 1 tests from All Tests
[==========] 1 test cases ran.
[ TOTAL ] PASS: 1 FAILURE: 0 ERROR: 0 TIME: 13 us
Test fixture can be divided into three categories:
- Independent Fixture
- Shared Fixture
- Global Fixture.
xUnit | BDD |
---|---|
FIXTURE | CONTEXT |
SETUP | BEFORE |
TEARDOWN | AFTER |
ASSERT_THAT | EXPECT |
#include <cut/cut.hpp>
FIXTURE(LengthTest)
{
Length length;
SETUP()
{}
TEARDOWN()
{}
TEST("length test1")
{}
TEST("length test2")
{}
};
Executing sequence:
Length
ConstructorSETUP
TEST("length test1")
TEARDOWN
Length
DestructorLength
ConstructorSETUP
TEST("length test2")
TEARDOWN
Length
Destructor
#include <cut/cut.hpp>
FIXTURE(LengthTest)
{
Length length;
BEFORE_CLASS()
{}
AFTER_CLASS()
{}
BEFORE()
{}
AFTER()
{}
TEST("length test1")
{}
TEST("length test2")
{}
};
Executing squence:
BEFORE_CLASS
Length
ConstructorBEFORE
TEST("length test1")
AFTER
Length
DestructorLength
ConstructorBEFORE
TEST("length test2")
AFTER
Length
DestructorAFTER_CLASS
#include <cut/cut.hpp>
BEFORE_ALL("before all 1")
{
}
BEFORE_ALL("before all 2")
{
}
AFTER_ALL("after all 1")
{
}
AFTER_ALL("after all 2")
{
}
#include <cut/cut.hpp>
FIXTURE(LengthTest)
{
Length length;
BEFORE_CLASS()
{}
AFTER_CLASS()
{}
BEFORE()
{}
AFTER()
{}
TEST("length test1")
{}
TEST("length test2")
{}
};
#include <cut/cut.hpp>
FIXTURE(VolumeTest)
{
Volume volume;
BEFORE_CLASS()
{}
AFTER_CLASS()
{}
BEFORE()
{}
AFTER()
{}
TEST("volume test1")
{}
TEST("volume test1")
{}
};
Possible executing squence:
BEFORE_ALL("before all 1")
BEFORE_ALL("before all 2")
LengthTest::BEFORE_CLASS
Length
ConstructorLengthTest::BEFORE
TEST("length test1")
LengthTest::AFTER
Length
DestructorLength
ConstructorLengthTest::BEFORE
TEST("length test2")
LengthTest::AFTER
Length
DestructorLengthTest::AFTER_CLASS
VolumeTest::BEFORE_CLASS
Volume
ConstructorLengthTest::BEFORE
TEST("volume test1")
LengthTest::AFTER
Volume
DestructorVolume
ConstructorLengthTest::BEFORE
TEST("volume test2")
LengthTest::AFTER
Volume
DestructorVolumeTest::AFTER_CLASS
AFTER_ALL("after all 2")
AFTER_ALL("after all 1")
#include <cut/cut.hpp>
#include <quantity/length/Length.h>
USING_CUM_NS
FIXTURE(LengthTest)
{
TEST("1 FEET should equal to 12 INCH")
{
ASSERT_THAT(Length(1, FEET), eq(Length(12, INCH)));
}
TEST("1 YARD should equal to 3 FEET")
{
ASSERT_THAT(Length(1, YARD), eq(Length(3, FEET)));
}
TEST("1 MILE should equal to 1760 YARD")
{
ASSERT_THAT(Length(1, MILE), eq(Length(1760, YARD)));
}
};
A independant and new instance RobotCleaner robot
will be created for each test case.
#include <cut/cut.hpp>
#include <robot-cleaner/RobotCleaner.h>
#include <robot-cleaner/Position.h>
#include <robot-cleaner/Instructions.h>
USING_CUM_NS
FIXTURE(RobotCleanerTest)
{
RobotCleaner robot;
TEST("at the beginning, the robot should be in at the initial position")
{
ASSERT_THAT(robot.getPosition(), is(Position(0, 0, NORTH)));
}
TEST("left instruction: 1-times")
{
robot.exec(left());
ASSERT_THAT(robot.getPosition(), is(Position(0, 0, WEST)));
}
TEST("left instruction: 2-times")
{
robot.exec(left());
robot.exec(left());
ASSERT_THAT(robot.getPosition(), is(Position(0, 0, SOUTH)));
}
};
#include <cut/cut.hpp>
#include <robot-cleaner/RobotCleaner.h>
#include <robot-cleaner/Position.h>
#include <robot-cleaner/Instructions.h>
USING_CUM_NS
FIXTURE(RobotCleanerTest)
{
RobotCleaner robot;
void WHEN_I_send_instruction(Instruction* instruction)
{
robot.exec(instruction);
}
void AND_I_send_instruction(Instruction* instruction)
{
WHEN_I_send_instruction(instruction);
}
void THEN_the_robot_cleaner_should_be_in(const Position& position)
{
ASSERT_THAT(robot.getPosition(), is(position));
}
TEST("at the beginning")
{
THEN_the_robot_cleaner_should_be_in(Position(0, 0, NORTH));
}
TEST("left instruction: 1-times")
{
WHEN_I_send_instruction(left());
THEN_the_robot_cleaner_should_be_in(Position(0, 0, WEST));
}
TEST("left instruction: 2-times")
{
WHEN_I_send_instruction(repeat(left(), 2));
THEN_the_robot_cleaner_should_be_in(Position(0, 0, SOUTH));
}
TEST("left instruction: 3-times")
{
WHEN_I_send_instruction(repeat(left(), 3));
THEN_the_robot_cleaner_should_be_in(Position(0, 0, EAST));
}
TEST("left instruction: 4-times")
{
WHEN_I_send_instruction(repeat(left(), 4));
THEN_the_robot_cleaner_should_be_in(Position(0, 0, NORTH));
}
};
ASSERT_THAT
makes assertions more expressive, it will be the actual value on the left, but the desired value on the right, and more consistent with English habits.
#include <cut/cut.hpp>
FIXTURE(CloseToTest)
{
TEST("close to double")
{
ASSERT_THAT(1.0, close_to(1.0, 0.5));
ASSERT_THAT(0.5, close_to(1.0, 0.5));
ASSERT_THAT(1.5, close_to(1.0, 0.5));
}
};
Hamcrest is a lightweight, extensible matcher framework that has been introduced into the JUnit framework by Beck Kent. cut introduces the design of Hamcrest, implements a C++ porting version of Hamcrest, which makes the assertion of cutg more scalable and readable.
Matcher | Description |
---|---|
anything | always matched |
_ | syntax sugar of anything |
#include <cut/cut.hpp>
USING_CUM_NS
FIXTURE(AnythingTest)
{
TEST("should always be matched")
{
ASSERT_THAT(1, anything<int>());
ASSERT_THAT(1u, anything<unsigned int>());
ASSERT_THAT(1.0, anything<double>());
ASSERT_THAT(1.0f, anything<float>());
ASSERT_THAT(false, anything<bool>());
ASSERT_THAT(true, anything<bool>());
ASSERT_THAT(nullptr, anything<std::nullptr_t>());
}
TEST("should support _ as syntactic sugar")
{
ASSERT_THAT(1u, _(int));
ASSERT_THAT(1.0f, _(float));
ASSERT_THAT(false, _(int));
ASSERT_THAT(nullptr, _(std::nullptr_t));
}
};
Matcher | Description |
---|---|
eq | equal to |
ne | not equal to |
lt | less than |
gt | greater than |
le | less or equal to |
ge | greater or equal to |
#include <cut/cut.hpp>
USING_CUM_NS
FIXTURE(EqualToTest)
{
TEST("should allow compare to integer")
{
ASSERT_THAT(0xFF, eq(0xFF));
ASSERT_THAT(0xFF, is(eq(0xFF)));
ASSERT_THAT(0xFF, is(0xFF));
ASSERT_THAT(0xFF == 0xFF, is(true));
}
TEST("should allow compare to bool")
{
ASSERT_THAT(true, eq(true));
ASSERT_THAT(false, eq(false));
}
TEST("should allow compare to string")
{
ASSERT_THAT("hello", eq("hello"));
ASSERT_THAT("hello", eq(std::string("hello")));
ASSERT_THAT(std::string("hello"), eq(std::string("hello")));
}
};
FIXTURE(NotEqualToTest)
{
TEST("should allow compare to integer")
{
ASSERT_THAT(0xFF, ne(0xEE));
ASSERT_THAT(0xFF, is_not(0xEE));
ASSERT_THAT(0xFF, is_not(eq(0xEE)));
ASSERT_THAT(0xFF != 0xEE, is(true));
}
TEST("should allow compare to boolean")
{
ASSERT_THAT(true, ne(false));
ASSERT_THAT(false, ne(true));
}
TEST("should allow compare to string")
{
ASSERT_THAT("hello", ne("world"));
ASSERT_THAT("hello", ne(std::string("world")));
ASSERT_THAT(std::string("hello"), ne(std::string("world")));
}
};
Matcher | Description |
---|---|
is | is decorator |
is_not | not decorator |
#include <cut/cut.hpp>
USING_CUM_NS
FIXTURE(IsNotTest)
{
TEST("integer")
{
ASSERT_THAT(0xFF, is_not(0xEE));
ASSERT_THAT(0xFF, is_not(eq(0xEE)));
}
TEST("string")
{
ASSERT_THAT("hello", is_not("world"));
ASSERT_THAT("hello", is_not(eq("world")));
ASSERT_THAT("hello", is_not(std::string("world")));
ASSERT_THAT(std::string("hello"), is_not(std::string("world")));
}
};
Matcher | Description |
---|---|
nil | null pointer |
#include <cut/cut.hpp>
USING_CUM_NS
FIXTURE(NilTest)
{
TEST("equal_to")
{
ASSERT_THAT(nullptr, eq(nullptr));
ASSERT_THAT(0, eq(NULL));
ASSERT_THAT(NULL, eq(NULL));
ASSERT_THAT(NULL, eq(0));
}
TEST("is")
{
ASSERT_THAT(nullptr, is(nullptr));
ASSERT_THAT(nullptr, is(eq(nullptr)));
ASSERT_THAT(0, is(0));
ASSERT_THAT(NULL, is(NULL));
ASSERT_THAT(0, is(NULL));
ASSERT_THAT(NULL, is(0));
}
TEST("nil")
{
ASSERT_THAT((void*)NULL, nil());
ASSERT_THAT((void*)0, nil());
ASSERT_THAT(nullptr, nil());
}
};
Matcher | Description |
---|---|
contains_string | contains a sub-string |
contains_string_ignoring_case | contains a sub-string ignoring case |
starts_with | starts with a sub-string |
starts_with_ignoring_case | starts with a sub-string ignoring case |
ends_with | ends with a sub-string |
ends_with_ignoring_case | ends with a sub-string ignoring case |
#include <cut/cut.hpp>
USING_CUM_NS
FIXTURE(StartsWithTest)
{
TEST("case sensitive")
{
ASSERT_THAT("ruby-cpp", starts_with("ruby"));
ASSERT_THAT("ruby-cpp", is(starts_with("ruby")));
ASSERT_THAT(std::string("ruby-cpp"), starts_with("ruby"));
ASSERT_THAT("ruby-cpp", starts_with(std::string("ruby")));
ASSERT_THAT(std::string("ruby-cpp"), starts_with(std::string("ruby")));
}
TEST("ignoring case")
{
ASSERT_THAT("ruby-cpp", starts_with_ignoring_case("Ruby"));
ASSERT_THAT("ruby-cpp", is(starts_with_ignoring_case("Ruby")));
ASSERT_THAT(std::string("ruby-cpp"), starts_with_ignoring_case("RUBY"));
ASSERT_THAT("Ruby-Cpp", starts_with_ignoring_case(std::string("rUBY")));
ASSERT_THAT(std::string("RUBY-CPP"), starts_with_ignoring_case(std::string("ruby")));
}
};
Matcher | Description |
---|---|
close_to | close to a float number |
nan | not a number |
#include <cut/cut.hpp>
#include <math.h>
USING_CUM_NS
FIXTURE(IsNanTest)
{
TEST("double")
{
ASSERT_THAT(sqrt(-1.0), nan());
ASSERT_THAT(sqrt(-1.0), is(nan()));
ASSERT_THAT(1.0/0.0, is_not(nan()));
ASSERT_THAT(-1.0/0.0, is_not(nan()));
}
};
TestOptions::TestOptions() : desc("cut")
{
desc.add({
{"help, h", "help message"},
{"filter, f", "--filter=pattern"},
{"color, c", "--color=[yes|no]"},
{"xml, x", "print test result into XML file"},
{"list, l", "list all tests without running them"},
{"progress, p", "print test result in progress bar"},
{"verbose, v", "verbosely list tests processed"},
{"repeat, r", "how many times to repeat each test"}
});
// default value
opt["color"] = "yes";
opt["repeat"] = "1";
}
Copyright (c) 2015-2020 Horance Liu. See LICENSE for details.