工作两年多了,一直采用TDD(测试驱动开发),刚开始觉得是反人类的方法论,后来在使用的过程中逐渐发现它的妙处。本文介绍了一些TDD的基本概念,并结合几个小需求进行实践。由于本人能力、精力有限,如有错误或者不当之处,还请各位提出宝贵的建议。
1. TDD原理
TDD流程.png
步骤:
- 先写测试代码,并执行,得到失败结果
- 写刚好让测试通过的代码,并通过测试用例
- 识别坏味道,重构代码,并保证测试通过
- 反复实行这个步骤,测试失败 -> 测试成功 -> 重构
三原则:
- 除非是为了使一个失败的用例通过,否则不允许编写任何代码
- 在一个单元测试中,只允许编写刚好能够导致失败的内容
- 只允许编写刚好能够使一个失败的用例通过的代码
详细的介绍详见参考文献1和2。
2. TDD实例
话不多说,下面通过一个实际的例子说明。由于最近在看STL,就实现一个简单的Array容器类,此例子可能不太贴切,但大体上是那么个过程。
该例子采用C++语言(为了方便,暂时将代码全部放在.h文件中)和谷歌的gtest测试框架(详见参考文献3)。完整的代码详见参考文献4,已经在Ubuntu 18.04调试通过,如有编译及运行问题,欢迎提出。
2.1 需求一
模仿STL,实现一个数据类型为int的Array类,且能够指定长度,此需求只要求实现其构造和析构函数。
按照TDD的步骤,我们首先写出测试用例(Test.cpp文件):
#include "IntArray.h"
#include "gtest/gtest.h"
struct IntArrayTest : testing::Test
{
};
TEST_F(IntArrayTest, test_constructor)
{
IntArray array{1};
ASSERT_EQ(0, array[0]);
}
此时执行代码,编译是失败的。
然后写刚好让测试通过的代码(IntArray.h文件),并通过测试用例:
#ifndef INTARRAY_H_
#define INTARRAY_H_
#include <cassert>
struct IntArray
{
IntArray() = default;
IntArray(int len) : len(len)
{
assert(this->len >= 0);
if(this->len > 0)
{
this->data = new int[this->len]{0};
}
}
~IntArray()
{
delete[] this->data;
}
int& operator [](int idx) const
{
assert(idx >= 0 and idx < this->len);
return this->data[idx];
}
private:
int len;
int* data;
};
#endif
至此实现了需求一,且代码和用例编译、运行通过。此时代码没有出现明显的坏味道,暂时不需要重构。但是此时有一个比较大的问题,不知各位有没有发现,由于我们的关注点不在此处,暂时不做解释,下文会有说明及修改。
2.2 需求二
实现一个size()方法,该方法返回IntArray的长度;实现一个erase()方法,该方法可以清除IntArray的所有内容。
其实需求二是两个小需求,首先写出测试用例一:
TEST_F(IntArrayTest, test_func_size)
{
IntArray array{3};
ASSERT_EQ(3, array.size());
}
</