本文通過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 = ["http://visibility:public"],
)
test/BUILD
在test
包下,描述測試用例的構建過程。
cc_test(
name = "quantity-test",
srcs = glob(["**/*.cpp", "**/*.cc"]),
copts = ["-Iexternal/cut/include"],
deps = [ "@cut//:cut",
"http://quantity:quantity" ],
)
quantity/BUILD
在quantity
包下,描述目標quantity
的構建過程。
cc_library(
name = "quantity",
srcs = glob(["**/*.cpp", "**/*.cc"]),
hdrs = glob(["**/*.h", "**/*.hpp"]),
visibility = ["http://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