提纲
- 单元测试的基本概念和重要性
- 单元测试可以无处不在
- 单元测试中什么最重要?
- 编码生成测试用例
- 单元测试框架
- 实例研究
单元测试的基本概念
- 软件质量的第一责任人是程序员,而非测试员
- 单元测试用来保证软件的基本质量,通过测试单个接口或者模块(一组接口)的基本功能来确保:
- 正确性:确保程序按照预期工作,尤其满足特定的边界条件
- 性能:确保空间复杂度和性能复杂度在预期范围内
- 无重大缺陷:确保无内存泄露、缓冲区溢出等重要缺陷
术语
- 测试:用于验证接口或者模块是否按照预期工作的活动/行为/代码
- 测试用例:用于测试特定功能的实例;一个测试用例通常包含一组输入数据以及对应的预期结果
- 测试套件(test suite):对多个测试用例的分组
- 测试方法(test method):如何测试某个接口或者模块,也就是如何设计和使用测试用例
- 测试框架(testing framework):专用于自动化单元测试的软件框架,如Glib Testing,GoogleTest等
单元测试的重要性
- 近几年各种开源基础软件的安全性漏洞造成的破坏性越来越大
- 2014年Openssl漏洞
- 2021年log4j漏洞
- 绝大多数开源软件缺乏基本的单元测试
- 开发者太过自信,不重视测试的重要性
- 急着开源后被动等待用户提issue的“开源协作”模式容易产生低质量代码
单元测试中什么最重要
- 测试用例:如何用尽可能少的测试用例覆盖尽可能多的边界条件?
- 测试方法:如何设计测试方法,使之
- 可以自动生成测试用例?或者,
- 可以自动生成预期结果?或者,
- 测试用例可由其他人无需编码即可维护?
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <assert.h>
#define DEFSZ_FACTORS 4
unsigned int *prime_factors(unsigned int natural, zise_t *nr_factors)
{
unsigned int *factors = NULL;
size_t sz = DEFSZ_FACTORS;
assert(nr_factors);
if(natural < 2)
{
goto failed;
}
factors = malloc(sz * sizeof(unsigned int));
if (factors == NULL)
goto failed;
*nr_factors = 0;
for(unsigned int u =2; u <=natural; u++)
{
if(natural % u == 0)
{
do{ natural = natural /u; }
while(natural % u == 0)
}
if(*nr_factors >= sz)
{
sz += DEFSZ-FACTORS;
facotrs = realloc(factors, sizeof(unsigned int))
if (factors == NULL)
goto failed;
}
factors[*nr_factors] = u;
*nr_factors = *nr_factors + 1;
}
return factors;
failed:
if(factors)
free(factors);
*nr_factors = 0;
return NULL;
}
#ifdef ENABLE_UNIT_TEST
#define SZ_TABLE(array) (sizeof(array)/sizeof(array[0]))
static unsigned int primes_under_20[] =
{
2,3,5,7,11,13,17,19
};
struct prime_factors{
unsigned int natural;
unsigned int nr_factors;
unsigned int factors[SZ_TABLE(primes_under_20)];
};
单元测试框架
- 单元测试框架只是一个辅助性工具
- 不能解决如何测试的问题
- 也不能自动生成测试用例
单元测试框架通常包含什么?
- 定义一个测试的便利方法
- 将测试用例组织成测试套件的便利方法
- 各种方便对比运行结果和预期结果的断言宏
- 测试报告(是否通过、运行时长等信息)
GLIB TESTING
- 使用夹具(Fixtures)的概念,用于封装测试对象以及测试数据。每一次测试前后,分别执行夹具的设置(set up)和拆卸(tear down)操作。
- 使用类似路径的形式定义测试套件和测试用例:/myobject/test1。
3. g_assert_true()
4. g_assert_cmpint(),g_assert_cmpuint()
5. g_assert_cmpfloat(),
g_assert_cmpfloat_with_epsilon()
6. g_assert_cmphex(),g_assert_cmpstr(),
g_assert_cmpmem(),
and a_assert_cmpvariant()
#include <glib.h>
#inlcude <locale.h>
typedef struct{
MyObject *obj;
OtherObject *helper;
}MyObjectFixture;
static void my_object_fixture_set_up(MyObjectFixture *fixture, gconstpointer user_data)
{
fixture->obj = my_object_new();
my_object_set_propl(fixture->obj, "some-value");
my_object_do_some_complex_setup(fixture->obj, user_data);
fixture->helper = other_object_new();
}
static void
my_object_fixture_tear_down(MyObjectFixture *fixture, gconstpointer user_data)
{
g_clear_object(&fixture->helper);
g_clear_object(&fixture->obj);
}
static test_my_object_test1(MyObjectFixture *fixture, gconstpointer user_data)
{
g_assert_cmpstr(my_object_get_property(fixture->obj), ==,xxx);
}
static test_my_object_test2(MyObjectFixture *fixture, gconstpointer user_data)
{
my_objcet_do_some_work_using_helper(fixture->helper, fixture->helper);
g_assert_cmpstr(my_object_get_property(fixture->obj), ==,xxx);
}
int main (int argc, char *argv[])
{
setlocale(LC_ALL, "");
g_test_int(&argc, &argv, NULL);
g_test_add("/my-object/test1", MyObjectFixture, "some-user",
mu_object_fixture_set_up, test_my_object_test1,
my_object_fixture_tear_down);
g_test_add("/my-object/test2", MyObjectFixture, "some-user",
mu_object_fixture_set_up, test_my_object_test2,
my_object_fixture_tear_down);
return g_test_run();
}
GOOGLETEST
- Google 的C++测试(testing )和模拟(mocking)框架,也可以用来测试c接口和模块
- 比GLib Testing 更加完善更强大
- 文档:http://goole.github.io/googletest/
|