在PA2中学到的有关编写测试用例的经验补充
来源:ysyx5期, PA2 基础设施(2)- “测试你的klib”
- 内存和字符串的写入函数, 例如
memset()
,strcpy()
等. - 内存和字符串的只读函数, 例如
memcmp()
,strlen()
等. - 格式化输出函数, 例如
sprintf()
等.
如何进行充分的测试:
1. 在可以遍历输入范围时,遍历输入范围
针对第一类函数, 我们应该如何构造一个测试场景, 使得存在一些方法来容易地得到测试输出呢? 注意这些函数都是对一个内存区域进行写入, 考虑如下的数组:
#define N 32
uint8_t data[N];
void reset() {
int i;
for (i = 0; i < N; i ++) {
data[i] = i + 1;
}
}
这样的一个数组, 每个元素都是1个字节, 而且它们的值都各不相同. 如果我们在这个数组上进行测试, 只要实际输出有1个字节不正确, 都可以大概率被检查出来. 为了得到预期的输出, 我们还要思考测试函数的预期行为: 以上函数都是对数组中的一段连续区间进行写入, 于是我们可以把预期的输出分成三段来检查:
- 第一段是函数写入区间的左侧, 这一段区间没有被写入, 因此应该有
assert(data[i] == i + 1)
- 第二段是函数写入的区间本身, 这一段区间的预期结果和函数的具体行为有关
- 第三段是函数写入区间的右侧, 这一段区间没有被写入, 因此应该有
assert(data[i] == i + 1)
于是我们可以编写两个辅助函数用于检查:
// 检查[l,r)区间中的值是否依次为val, val + 1, val + 2...
void check_seq(int l, int r, int val) {
int i;
for (i = l; i < r; i ++) {
assert(data[i] == val + i - l);
}
}
// 检查[l,r)区间中的值是否均为val
void check_eq(int l, int r, int val) {
int i;
for (i = l; i < r; i ++) {
assert(data[i] == val);
}
}
有了这两个函数, 我们就可以遍历各种输入, 并且很容易地编写出测试函数的预期输出了. 例如针对memset()
, 我们可以编写如下的测试代码:
void test_memset() { int l, r; for (l = 0; l < N; l ++) { for (r = l + 1; r <= N; r ++) { reset(); uint8_t val = (l + r) / 2; memset(data + l, val, r - l); check_seq(0, l, 1); check_eq(l, r, val); check_seq(r, N, r + 1); } } }
2. 在输入范围过大时,我们选取有代表性的输入
最后我们来看格式化输出函数. 以%d
为例, 我们需要构造一些输入. 但整数的范围太大了, 不能全部遍历它们, 因此我们需要挑选一些有代表性的整数. limits.h
这个C标准头文件里面包含了一些最大数和最小数的定义, 你可以打开/usr/include/limits.h
来阅读它们. 一些有代表性的整数可以是:
int data[] = {0, INT_MAX / 17, INT_MAX, INT_MIN, INT_MIN + 1,
UINT_MAX / 17, INT_MAX / 17, UINT_MAX};
为了得到相应的预期输出, 我们可以先编写一个native程序来用printf输出它们, 然后把输出结果整理到测试代码里面. cpu-tests
中的预期输出也是这样生成的.