An open API service indexing awesome lists of open source software.

https://github.com/horance-liu/cut

Cut(C++ Unified Test) is a simple xUnit implements in modern c++11, which originated from magellan.
https://github.com/horance-liu/cut

Last synced: 23 days ago
JSON representation

Cut(C++ Unified Test) is a simple xUnit implements in modern c++11, which originated from magellan.

Awesome Lists containing this project

README

        

# CUT: C++ Unified Test Framework

## 灵感

Cut是一个简单的、可扩展的、使用C\+\+11实现的xUnit测试框架。Cut设计灵感来自于Java社区著名的测试框架JUnit。

## 安装

### 编译环境

- 支持的平台:
* [MAC OS X] supported
* [Linux] supported
* [Windows] partial supported

- 支持的编译器:
* [CLANG] 3.4 or later.
* [GCC] 4.8 or later.
* [Cygwin or MinGW] supported.
* [MSVC] not supported.

### 安装Cut

Cut支持四种方式安装:

- 在线安装
- 手动安装

因为Cut能够测试自身,为此强烈**推荐**使用「在线安装」,简单,便捷,不易出错。

### 在线安装

```bash
$ curl -fsSL https://raw.github.com/ccock/cut/master/install.sh | sh
```

### 手动安装和测试Cut

```bash
$ mkdir tmp && cd tmp
$ cmake .. && make
$ sudo make install
```

##### 测试cut

```bash
$ cd tmp
$ cmake -DENABLE_TEST=on .. && make
$ test/cut-test
```

### Windows构建

##### MinGW

```bash
$ cmake -G"MinGW Makefiles" ..
$ make
$ make install
```

##### Cygwin

```bash
$ cmake ..
$ make
$ make install
```

### 破冰之旅

##### 物理目录

```bash
quantity
├── include
│   └── quantity
├── src
│   └── quantity
└── test
│ ├── main.cpp
└── CMakeLists.txt
```

##### main函数

```cpp
#include

int main(int argc, char** argv)
{
return cut::run_all_tests(argc, argv);
}
```

##### CMakeList脚本

```cmake
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)
```

其中:

- [cut](https://github.com/ccock/cut): C++ Unified Test Framework.

##### 构建

```bash
$ mkdir build
$ cd build
$ cmake ..
$ make
```

##### 运行

```bash
$ ./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
```

### 体验cut

#### 第一个用例

```cpp
#include
#include

USING_CUM_NS

FIXTURE(LengthTest)
{
TEST("1 FEET should equal to 12 INCH")
{
ASSERT_THAT(Length(1, FEET), eq(Length(12, INCH)));
}
};
```

使用`cut`时,只需要包含`cut.hpp`一个头文件即可。`cut`使用`cum`的断言机制, 使得断言更加统一、自然,且具有良好的扩展性;使用 `USING_CUM_NS`, 从而可以使用 `eq` 代替 `cum::eq`,简短明确;除非出现名字冲突,否则推荐使用简写的`eq`。

#### Length实现

```cpp
// quantity/Length.h
#include

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;
};
```

```cpp
// quantity/Length.cpp
#include

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);
}
```

##### 构建

```bash
$ mkdir build
$ cd build
$ cmake ..
$ make
```

##### 运行

```bash
$ ./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
```

### Fixture

FIXTURE的参数可以是任意的C\+\+标识符。一般而言,将其命名为CUT(Class Under Test)的名字即可。根据作用域的大小,Fixture可分为三个类别:独立的Fixture,共享的Fixture,全局的Fixture。

#### 支持BDD风格

| xUnit | BDD |
| ------------- |---------------|
| FIXTURE | CONTEXT |
| SETUP | BEFORE |
| TEARDOWN | AFTER |
| ASSERT_THAT | EXPECT |

#### 独立的Fixture

```cpp
#include

FIXTURE(LengthTest)
{
Length length;

SETUP()
{}

TEARDOWN()
{}

TEST("length test1")
{}

TEST("length test2")
{}
};
```

执行序列为:
1. `Length` 构造函数
2. `SETUP`
3. `TEST("length test1")`
4. `TEARDOWN`
5. `Length` 析构函数
6. `Length` 构造函数
7. `SETUP`
8. `TEST("length test2")`
9. `TEARDOWN`
10. `Length` 析构函数

#### 共享的Fixture

```cpp
#include

FIXTURE(LengthTest)
{
Length length;

BEFORE_CLASS()
{}

AFTER_CLASS()
{}

BEFORE()
{}

AFTER()
{}

TEST("length test1")
{}

TEST("length test2")
{}
};
```

执行序列为:
1. `BEFORE_CLASS`
2. `Length` 构造函数
3. `BEFORE`
4. `TEST("length test1")`
5. `AFTER`
6. `Length` 析构函数
7. `Length` 构造函数
8. `BEFORE`
9. `TEST("length test2")`
10. `AFTER`
11. `Length` 析构函数
12. `AFTER_CLASS`

#### 全局的Fixture

有时候需要在所有用例启动之前完成一次性的全局性的配置,在所有用例运行完成之后完成一次性的清理工作。cut则使用`BEFORE_ALL`和`AFTER_ALL`两个关键字来支持这样的特性。

```cpp
#include

BEFORE_ALL("before all 1")
{
}

BEFORE_ALL("before all 2")
{
}

AFTER_ALL("after all 1")
{
}

AFTER_ALL("after all 2")
{
}
```

`BEFORE_ALL`和`AFTER_ALL`向系统注册`Hook`即可,cut便能自动地发现它们,并执行它们。犹如C\+\+不能保证各源文件中全局变量初始化的顺序一样,避免在源文件之间的`BEFORE_ALL`和`AFTER_ALL`设计不合理的依赖关系。

```cpp
#include

FIXTURE(LengthTest)
{
Length length;

BEFORE_CLASS()
{}

AFTER_CLASS()
{}

BEFORE()
{}

AFTER()
{}

TEST("length test1")
{}

TEST("length test2")
{}
};
```

```cpp
#include

FIXTURE(VolumeTest)
{
Volume volume;

BEFORE_CLASS()
{}

AFTER_CLASS()
{}

BEFORE()
{}

AFTER()
{}

TEST("volume test1")
{}

TEST("volume test1")
{}
};
```

cut可能的一个执行序列为:

1. `BEFORE_ALL("before all 1")`
2. `BEFORE_ALL("before all 2")`
3. `LengthTest::BEFORE_CLASS`
4. `Length`构造函数
5. `LengthTest::BEFORE`
6. `TEST("length test1")`
7. `LengthTest::AFTER`
8. `Length`析构函数
9. `Length`构造函数
10. `LengthTest::BEFORE`
11. `TEST("length test2")`
12. `LengthTest::AFTER`
13. `Length`析构函数
14. `LengthTest::AFTER_CLASS`
15. `VolumeTest::BEFORE_CLASS`
16. `Volume`构造函数
17. `LengthTest::BEFORE`
18. `TEST("volume test1")`
19. `LengthTest::AFTER`
20. `Volume`析构函数
21. `Volume`构造函数
22. `LengthTest::BEFORE`
23. `TEST("volume test2")`
24. `LengthTest::AFTER`
25. `Volume`析构函数
26. `VolumeTest::AFTER_CLASS`
27. `AFTER_ALL("after all 2")`
28. `AFTER_ALL("after all 1")`

### 用例设计

#### 自动标识

cut能够自动地实现测试用例的标识功能,用户可以使用字符串来解释说明测试用例的意图,使得用户在描述用例时更加自然和方便。

```cpp
#include
#include

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)));
}
};
```

#### 面向对象

cut实现xUnit时非常巧妙,使得用户设计用例时更加面向对象。`RobotCleaner robot`在每个用例执行时都将获取一个独立的、全新的实例。

```cpp
#include
#include
#include
#include

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)));
}
};
```

#### 函数提取

提取的相关子函数,可以直接放在`Fixture`的内部,使得用例与其的距离最近,更加体现类作用域的概念。

```cpp
#include
#include
#include
#include

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

cut只支持一种断言原语:`ASSERT_THAT`, 从而避免用户在选择`ASSERT_THAT/ASSERT_NE, ASSERT_TRUE/ASSERT_FALSE`时的困扰,使其断言更加具有统一性,一致性。

此外,`ASSERT_THAT`使得断言更加具有表达力,它将实际值放在左边,期望值放在右边,更加符合英语习惯。

```cpp
#include

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

Hamcrest是Java社区一个轻量级的,可扩展的Matcher框架,曾被Kent Beck引入到JUnit框架中,用于增强断言的机制。cut引入了Hamcrest的设计,实现了一个C\+\+移植版本的Hamcrest,使得cutg的断言更加具有扩展性和可读性。

##### anything

| 匹配器 | 说明 |
| ------------- |---------------|
| anything | 总是匹配 |
| _ | anything语法糖 |

```cpp
#include

USING_CUM_NS

FIXTURE(AnythingTest)
{
TEST("should always be matched")
{
ASSERT_THAT(1, anything());
ASSERT_THAT(1u, anything());
ASSERT_THAT(1.0, anything());
ASSERT_THAT(1.0f, anything());
ASSERT_THAT(false, anything());
ASSERT_THAT(true, anything());
ASSERT_THAT(nullptr, anything());
}

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));
}
};
```

##### 比较器

| 匹配器 | 说明 |
| ------------- |----------------|
| eq | 相等 |
| ne | 不相等 |
| lt | 小于 |
| gt | 大于 |
| le | 小于或等于 |
| ge | 大于或等于 |

```cpp
#include

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")));
}
};
```

##### 修饰器

| 匹配器 | 说明 |
| ------------- |----------------|
| is | 可读性装饰器 |
| is_not | 可读性装饰器 |

```cpp
#include

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")));
}
};
```

##### 空指针

| 匹配器 | 说明 |
| ------------- |----------------|
| nil | 空指针 |

```cpp
#include

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());
}
};
```

##### 字符串

| 匹配器 | 说明 |
| ----------------------------- |----------------------------|
| contains_string | 断言是否包含子串 |
| contains_string_ignoring_case | 忽略大小写,断言是否包含子 |
| starts_with | 断言是否以该子串开头 |
| starts_with_ignoring_case | 忽略大小写,断言是否以该子串开头 |
| ends_with | 断言是否以该子串结尾 |
| ends_with_ignoring_case | 忽略大小写,断言是否以该子串结尾 |

```cpp
#include

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")));
}
};
```

##### 浮点数

| 匹配器 | 说明 |
| ---------|----------------------|
| close_to | 断言浮点数近似等于 |
| nan | 断言浮点数不是一个数字 |

```cpp
#include
#include

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()));
}
};
```

### 程序选项

```cpp
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";
}
```