本文通过Bazel(Google Build Tool)构建工具,使用Cut(C++ Unified Test Framework)快速构建一个C++程序的TDD环境,以此阐述Cut在实际项目中的实际应用。
一般地,一个C++程序为了实施TDD,必须先安装测试框架(例如Cut)到系统目录(一般地,默认为/usr/local
),但这样可能造成复杂的版本管理问题。
这个案例实践中,只需要安装Bazel构建工具,无需事先安装Cut的依赖,便可以开始TDD之旅了。因为Bazel为工程建立独立的构建环境,并且按照依赖规则,会自动下载,并构建外部依赖,例如Cut测试框架。
其中,Bazel是典型的声明式构建工具,相对于Make工具,具有良好的用户友好性。但其执行过程必然是命令式的,其背后的本质必然与Make的过程相似。
安装Bazel
参考Bazel的官方网站:https://bazel.build,查阅Bazel安装相关安装手册,在此不再冗述。
需求
通过一个简单的例子,讲解一个典型的C++程序如何使用Cut进行TDD开发。需求非常简单,存在两个单位计量体系,实现一个单位数值比较的程序库。
- Length:
1 Mile == 1760 Yard
1 Yard == 3 Feet
1 Feet == 12 Inch
- Volume:
1 TBSP == 3 TSP
1 OZ == 2 TBSP
工程构建
Quantity使用Bazel构建,其物理结构如下图所示。
quantity
├── WORKSPACE
├── cut.BUILD
├── quantity
│ ├── BUILD
│ ├── base
│ │ ├── Amount.h
│ │ ├── Quantity.h
│ │ └── Quantity.hpp
│ ├── length
│ │ ├── Length.cpp
│ │ └── Length.h
│ └── volume
│ ├── Volume.cpp
│ └── Volume.h
└── test
├── BUILD
├── LengthTest.cpp
├── VolumeTest.cpp
└── main.cpp
WORKSPACE
使用WORKSPACE,声明项目quantity
依赖于cut
,它在执行测试时,会自动去Github
下载源代码,并执行编译。
new_http_archive(
name = "cut",
url = "https://github.com/horance-liu/cut/archive/release-1.0.0.tar.gz",
build_file = "cut.BUILD",
strip_prefix = "cut-release-1.0.0",
)
cut.BUILD
描述Cut
的构建过程。
cc_library(
name = "cut",
srcs = glob(["src/**/*.cpp"]),
hdrs = glob(["include/**/*.h"]),
copts = ["-Iexternal/cut/include"],
visibility = ["//visibility:public"],
)
test/BUILD
在test
包下,描述测试用例的构建过程。
cc_test(
name = "quantity-test",
srcs = glob(["**/*.cpp", "**/*.cc"]),
copts = ["-Iexternal/cut/include"],
deps = [ "@cut//:cut",
"//quantity:quantity" ],
)
quantity/BUILD
在quantity
包下,描述目标quantity
的构建过程。
cc_library(
name = "quantity",
srcs = glob(["**/*.cpp", "**/*.cc"]),
hdrs = glob(["**/*.h", "**/*.hpp"]),
visibility = ["//visibility:public"],
)
实现
Base组件
quantity/base/Amount.h
#ifndef H21E7D6D3_9F51_40E8_957C_72D0DBF81D69
#define H21E7D6D3_9F51_40E8_957C_72D0DBF81D69
using Amount = unsigned int;
#endif
quantity/base/Quantity.h
#ifndef HE781FE8C_8C1B_490C_893C_B3412F6CB478
#define HE781FE8C_8C1B_490C_893C_B3412F6CB478
#include "quantity/base/Amount.h"
template <typename Unit>
struct Quantity
{
Quantity(Amount amount, Unit unit);
bool operator==(const Quantity&) const;
bool operator!=(const Quantity&) const;
private:
const Amount amountInBaseUnit;
};
#endif
quantity/base/Quantity.hpp
#include "quantity/base/Quantity.h"
template <typename Unit>
Quantity<Unit>::Quantity(Amount amount, Unit unit)
: amountInBaseUnit(unit * amount)
{
}
template <typename Unit>
bool Quantity<Unit>::operator==(const Quantity& rhs) const
{
return amountInBaseUnit == rhs.amountInBaseUnit;
}
template <typename Unit>
bool Quantity<Unit>::operator!=(const Quantity& rhs) const
{
return !(*this == rhs);
}
Length组件
length/Length.h
#ifndef HF21A561D_09DF_4686_935D_4B7CD6FD9A2B
#define HF21A561D_09DF_4686_935D_4B7CD6FD9A2B
enum LengthUnit
{
INCH = 1,
FEET = 12 * INCH,
YARD = 3 * FEET,
MILE = 1760 * YARD,
};
using Length = Quantity<LengthUnit>;
#endif
length/Length.cpp
#include "quantity/length/Length.h"
#include "quantity/base/Quantity.hpp"
template struct Quantity<LengthUnit>;
Volume组件
volume/Volume.h
#ifndef HA0A7C92D_2A2A_47D0_B89D_ED2AFF645F23
#define HA0A7C92D_2A2A_47D0_B89D_ED2AFF645F23
#include "quantity/base/Quantity.h"
enum VolumeUnit
{
TSP = 1,
TBSP = 3 * TSP,
OZ = 2 * TBSP,
};
using Volume = Quantity<VolumeUnit>;
#endif
volume/Volume.cpp
#include "quantity/volume/Volume.h"
#include "quantity/base/Quantity.hpp"
template struct Quantity<VolumeUnit>;
测试用例
test/LengthTest.cpp
#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 feets")
{
ASSERT_THAT(Length(1, YARD), eq(Length(3, FEET)));
}
TEST("1 mile should equal to 1760 yards")
{
ASSERT_THAT(Length(1, MILE), eq(Length(1760, YARD)));
}
};
volume/VolumeTest.cpp
#include "cut/cut.hpp"
#include "quantity/volume/Volume.h"
USING_CUM_NS
FIXTURE(VolumeTest)
{
TEST("1 tbsp should equal to 3 tsps")
{
ASSERT_THAT(Volume(1, TBSP), eq(Volume(3, TSP)));
}
TEST("1 oz should equal to 2 tbsps")
{
ASSERT_THAT(Volume(1, OZ), eq(Volume(2, TBSP)));
}
};
test/main.cpp
#include "cut/cut.hpp"
int main(int argc, char** argv)
{
return cut::run_all_tests(argc, argv);
}
运行测试
$ bazel test //quantity:quantity-test