TDD 使用 Cut 快速构建 C++ 程序的 TDD 环境

horance · December 31, 2020 · 13 hits

本文通过 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

Github

「软件匠艺社区」旨在传播匠艺精神,通过分享好的「工作方式」,让帮助程序员更加快乐高效地编程!

No Reply at the moment.
You need to Sign in before reply, if you don't have an account, please Sign up first.