-
理解单元测试主要内容(转)
2010-03-01 10:27:33
单元测试大多数由开发人员来完成,测试人员技术背景较好或者开发系统软件时可能会安排测试人员进行单元测试,大多数进行的单元测试都是开发人员调试程序或者开发组系统联合调试的过程。单元测试一般包括五个方面的测试:
一、模块接口测试
模块接口测试是单元测试的基础。只有在数据能正确流入、流出模块的前提下,其他测试才有意义。模块接口测试也是集成测试的重点,这里进行的测试主要是为后面打好基础。
测试接口正确与否应该考虑下列因素:
1、输入的实际参数与形式参数的个数是否相同;
2、输入的实际参数与形式参数的属性是否匹配;
3、输入的实际参数与形式参数的量纲是否一致;
4、调用其他模块时所给实际参数的个数是否与被调模块的形参个数相同;
5、调用其他模块时所给实际参数的属性是否与被调模块的形参属性匹配;
6、调用其他模块时所给实际参数的量纲是否与被调模块的形参量纲一致;
7、调用预定义函数时所用参数的个数、属性和次序是否正确;
8、是否存在与当前入口点无关的参数引用;
9、是否修改了只读型参数;
10、对全程变量的定义各模块是否一致;
11、是否把某些约束作为参数传递。
如果模块功能包括外部输入输出,还应该考虑下列因素:
1、文件属性是否正确;
2、OPEN/CLOSE语句是否正确;
3、格式说明与输入输出语句是否匹配;
4、缓冲区大小与记录长度是否匹配;
5、文件使用前是否已经打开;
6、是否处理了文件尾;
7、是否处理了输入/输出错误;
8、输出信息中是否有文字性错误。
二、局部数据结构测试
检查局部数据结构是为了保证临时存储在模块内的数据在程序执行过程中完整、正确,局部功能是整个功能运行的基础。重点是一些函数是否正确执行,内部是否运行正确。
局部数据结构往往是错误的根源,应仔细设计测试用例,力求发现下面几类错误:
1、不合适或不相容的类型说明;
2、变量无初值;
3、变量初始化或省缺值有错;
4、不正确的变量名(拼错或不正确地截断);
5、出现上溢、下溢和地址异常。
三、边界条件测试
边界条件测试是单元测试中最重要的一项任务。众所周知,软件经常在边界上失效,采用边界值分析技术,针对边界值及其左、右设计测试用例,很有可 能发现新的错误。边界条件测试是一项基础测试,也是后面系统测试中的功能测试的重点,边界测试执行的较好,可以大大提高程序健壮性。
四、模块中所有独立路径测试
在模块中应对每一条独立执行路径进行测试,单元测试的基本任务是保证模块中每条语句至少执行一次。测试目的主要是为了发现因错误计算、不正确的比较和不适当的控制流造成的错误。具体做法就是程序员逐条调试语句。
常见的错误包括:
1、误解或用错了算符优先级;
2、混合类型运算;
3、变量初值错;
4、精度不够;
5、表达式符号错。
比较判断与控制流常常紧密相关,测试时注意下列错误:
1、不同数据类型的对象之间进行比较;
2、错误地使用逻辑运算符或优先级;
3、因计算机表示的局限性,期望理论上相等而实际上不相等的两个量相等;
4、比较运算或变量出错;
5、循环终止条件或不可能出现;
6、迭代发散时不能退出;
7、错误地修改了循环变量。
五、模块的各条错误处理通路测试
程序在遇到异常情况时不应该退出,好的程序应能预见各种出错条件,并预设各种出错处理通路。如果用户不按照正常操作,程序就退出或者停止工作,实际上也是一种缺陷,因此单元测试要测试各种错误处理路径。
一般这种测试着重检查下列问题:
1、输出的出错信息难以理解;
2、记录的错误与实际遇到的错误不相符;
3、在程序自定义的出错处理段运行之前,系统已介入;
4、异常处理不当;
5、错误陈述中未能提供足够的定位出错信息。
-
纠正对单元测试的五个错误认识
2009-03-07 23:36:55
纠正对单元测试的五个错误认识
如果实施得当,单元测试可以帮助开发团队更快地交付应用程序,换言之,它能够提高企业的竞争力。然而,只有很少的企业尝试过使用单元测试,而其中有只有一小部分成功地将其作为标准在企业范围内推广。为什么会这样?通常,这是由软件开发人员和管理人员对单元测试的作用,以及维持单元测试所需付出代价的错误认识所造成的。
以下就阻碍软件开发团队从单元测试这一强大的软件验证方法中受益的五个最常见的错误认识:
错误1:我们已经在做单元测试
每个人对“单元测试”都有不同的认识,不过大多数业界专家普遍认为,单元测试应该是测试应用程序的基础组成部分,即代码单元。换句话说,这是API 层次上的测试。一些团队声称他们在做单元测试,而实际上他们做的是系统测试,或者是所谓的“开发测试(dev test)”。还有一些团队做了部分API层次的测试,但他们并没有把单元测试作为开发过程中的必要组成部分。
除非团队把持续进行API层次的单元测试作为开发过程中不可缺少的组成部分,否则单元测试必然会随着日程与预算带来压力的提升、政策与项目的发展,以及人员的流动而灭亡。
那些极少数长期保持傲人业绩的企业正是把单元测试安排为例行任务的企业。因此,不但要利用自动化测试来保证单元测试尽量全面、顺畅、高效地执行,还要为保证这一质量过程能够长期执行并根据需求而调整来确定基本的工作规范,比如把各个报告中的问题直接指向负责的开发人员,以及让管理人员能够及时方便地看到各开发人员的测试任务分配情况等。
错误2:自动测试并没多大用处
许多开发人员认为,除非是亲自编写单元测试,否则其一点利用价值都没有。这就大错特错了。由于生成测试的方式与算法的发展,测试工具也变得越来越有效。即使是最基本的自动化方法,也可以在很短时间里生成几千个原来根本无法完成的测试。这可是毫不费力就可以得到的好处。
除了可以给你生成测试,甚至“免费”帮你找到一些缺陷,自动测试还能让你集中精力进行那些更重要、更复杂、更全面的测试,那些真正需要专业技术的测试。
当前工具所能提供的高层次的自动测试能够显著减少开发团队在这些方面的工作,从而节省大量的时间与精力。如果没有这些工具的帮助,单元测试可能会消耗团队的大量资源,而这正是让许多团队认为单元测试是一种理论上有用但实际却很难实行的测试方法的原因所在。
错误3:要做的只是购买一个优秀的单元测试工具
我见过许多团队在购买测试工具时把它当做实现目标或完成任务的万能药。他们不想在其上花费任何精力,不对其进行任何设置,也不将其作为工作日程的一部分。到最后,他们自然也无法得到理想的结果。他们以为单元测试没有给他们带来任何好处,而实际上他们并没有执行真正意义上的单元测试——而只是在空谈。
单元测试工具并不是可以解决所有问题的王牌。事实上,它只是一个开始。开发人员需要的不仅仅是工具,他们还需要适当的指导、训练、支持设施和工作流程。如果真的希望这个工具能够成为开发过程的一部分,你就得积极主动地学习和使用它,寻找让它能在既定工作环境下发挥作用的办法,并保证这些使用方法都简单易行。目前有许多可以使用的工具,但是除非测试团队真正去使用它——部署、扩展并根据情况进行调整,否则你买到的只是“闲置工具”而已。
错误4:自动测试达到75%的覆盖率——任务完成
许多人认为,如果自动测试工具能够生成近75%覆盖率的测试,他就能跟老板说自动测试已经完成了。这绝对是个错误的想法。虽然这是一个非常好的开始,但你不能仅仅因为这一点就认为已经做完单元测试。实际上你只是刚刚开始而已,你还需要验证软件的具体功能。
自动测试很有用,但必须让这些测试与需求挂钩。为了实现这一点,你可以检验并了解这些工具给你生成的测试,然后利用这些免费“礼物”去实现更多的价值。
大多数自动测试工具都会提供一些工具集,使你能够扩展这些自动生成的测试。比如Parasoft Jtest里就有对象库、桩、测试用例参数化和追踪工具Tracer(一个只需在应用程序中运行用例便可以生成功能测试用例的工具)。
错误5:单元测试不值得费工夫
每个了解单元测试的人都知道,这绝不是一份简单的工作,但这并不意味着不值得在这方面花费工夫。
单元测试确实有很高的门槛:测试团队必须了解什么是单元测试、怎样进行单元测试、用它测试什么,以及如何使用工具简化单元测试。如果测试团队真的对单元测试不感兴趣、不了解,甚至根本没有时间开始尝试它,那么他们可能就不会感觉到单元测试的紧迫性。有些团队可能认识到单元测试的重要性,但他们只是被问题缠得团团转,而没有花时间在真正的方向上迈出第一步。归根到底,这需要对单元测试的价值、对质量方面的责任,以及它将为项目争取到的额外时间有一个清楚的认识。
那么,是什么让单元测试的价值超过为此付出的努力呢?单元测试的一个很大优势在于,发现问题的时间越早,在后期遇到的深层错误就越少。这里说的深层错误是指那些并没有形成真正破坏,但它们在API里隐藏得越来越深,直到最后诱发真正问题的错误。这种情况发生的时候,通常很难发现问题的源头。即使你找到源头,也会发现已经有太多的代码层依赖于这个API。
如果正确地进行单元测试,验证代码能够准确地实现功能,就能更早、更有效地发现问题。如果能及早发现问题,缺陷就不会被带入源代码库中,也就是说以后也无需再对它进行修改——通常这时再修改的困难度、成本与时间都会指数叠加。
从我的经验来看,那些真正采用单元测试的开发人员最终都能写出更为优秀的代码,工作效率自然也更高。而原因则在于他们不会被各种缺陷绕得团团转,无须一次又一次地重写同一段代码。任何接受了单元测试,并将其作为所有开发项目标准来实践的企业都能显著地提高质量,减少软件开发过程中的缺陷,并最终获得巨大的竞争优势。 -
代码覆盖率测试
2008-12-10 10:52:27
昨天开始研究了一下阿LTP测试工具,这个工具可以做linux kernel测试,也可以用来做代码覆盖测试,重点介绍gcov和lcov这两个工具
测试代码在附件中,大家可以尝试一下。
在编译的时候要加上-fprofile-arcs -ftest-coverage
例如:gcc -o test queue.h queue.c use.c -fprofile-arcs -ftest-coverage
执行以后,能看到生成这些文件:
test mymemory.log queue.c queue.c.gcov queue.gcda queue.gcno queue.h queue.h.gch use.c use.gcda use.gcno
如果安装了,ggcov的话,直接在当前目录下./ggcov . 就行了,你可以看到每个文件的覆盖程度,也可以看到每个函数的覆盖程度
如果没有ggcov的话,也没关系,就做个手动的,拿上面的use.c来举个例子,只要执行gcov use.c就行了,你ls一下,会看到多出这样一个文件夹 use.c.gcov
打开use.c.gcov
-: 0:Source:use.c
-: 0:Graph:use.gcno
-: 0:Data:use.gcda
-: 0:Runs:1
-: 0:Programs:1
-: 1:#include <stdlib.h>
-: 2:#include <stdio.h>
-: 3:#include <mcheck.h>
-: 4:#include "queue.h"
-: 5:
-: 6:
-: 7:int main()
function main called 1 returned 100% blocks executed 97%
1: 8:{
-: 9: Queue line;
-: 10: Item temp;
-: 11: char ch;
-: 12:
1: 13: mtrace();
call 0 returned 100%
-: 14:
1: 15: InitializeQueue(&line);
call 0 returned 100%
-: 16:
1: 17: puts("testing the queue .Type a to add a value");
call 0 returned 100%
1: 18: puts("type d to delete a value , and type q to quit");
call 0 returned 100%
-: 19:
19: 20: while((ch = getchar()) != 'q')
call 0 returned 100%
branch 1 taken 94%
branch 2 taken 6% (fallthrough)
-: 21: {
17: 22: if(ch != 'a' && ch != 'd')
branch 0 taken 76% (fallthrough)
branch 1 taken 24%
branch 2 taken 62% (fallthrough)
branch 3 taken 38%
8: 23: continue;
-: 24:
-: 25:
9: 26: if(ch == 'a')
branch 0 taken 44% (fallthrough)
branch 1 taken 56%
-: 27: {
4: 28: printf("Integer to add: ");
call 0 returned 100%
4: 29: scanf("%d",&temp);
call 0 returned 100%
-: 30:
4: 31: if(!QueueIsFull(&line))
call 0 returned 100%
branch 1 taken 100% (fallthrough)
branch 2 taken 0%
-: 32: {
4: 33: printf("putting %d into queue \n", temp);
call 0 returned 100%
4: 34: EnQueue(temp, &line);
call 0 returned 100%
-: 35: }
-: 36: else
#####: 37: puts("Queue is full");
call 0 never executed
-: 38: }
-: 39: else
-: 40: {
5: 41: if (QueueIsEmpty(&line))
call 0 returned 100%
branch 1 taken 20% (fallthrough)
branch 2 taken 80%
1: 42: puts ("Nothing to delete");
call 0 returned 100%
-: 43: else
-: 44: {
4: 45: DeQueue(&temp, &line);
call 0 returned 100%
4: 46: printf("Removeing %d from queue \n",temp);
call 0 returned 100%
-: 47: }
-: 48: }
-: 49:
-: 50:
9: 51: printf("%d items in queue \n",QueueItemCount(&line));
call 0 returned 100%
call 1 returned 100%
9: 52: puts("Type a to add . d to delete, q to quit:");
call 0 returned 100%
-: 53: }
1: 54: EmptyTheQueue(&line);
call 0 returned 100%
1: 55: puts("Bye!");
call 0 returned 100%
-: 56:
1: 57: return 0;
-: 58:}
凡是带有“######”,说明该代码没有被覆盖,要看看你的测试用例了
居然不让我上传附件,大家就看看源代码吧!
queue.h
#pragma c9x on
#ifndef _QUEUE_H_
#define _QUEUE_H_
#include <stdbool.h>
#define MAXQUEUE 5
typedef int Item;
typedef struct node
{
Item item;
struct node *next;
}Node;
typedef struct queue
{
Node * front;
Node * rear;
int items;
}Queue;
void InitializeQueue(Queue *pq);
bool QueueIsFull (const Queue *pq);
bool QueueIsEmpty (const Queue *pq);
int QueueItemCount(const Queue *pq);
bool EnQueue(Item item,Queue *pq);
bool DeQueue(Item *pitem, Queue *pq);
void EmputyTheQueue(Queue *pq);
#endif
queue.c 源代码
#include <stdio.h>
#include <stdlib.h>
#include <mcheck.h>
#include "queue.h"
static void CopyToNode (Item item, Node *pn);
static void CopyToItem (Node *pn, Item *pi);
void InitializeQueue(Queue *pq)
{
pq -> front = pq ->rear = NULL;
pq ->items = 0;
}
bool QueueIsFull (const Queue *pq)
{
return pq ->items == MAXQUEUE;
}
bool QueueIsEmpty(const Queue *pq)
{
return pq->items == 0;
}
int QueueItemCount(const Queue *pq)
{
return pq ->items;
}
bool EnQueue(Item item, Queue *pq)
{
Node *pnew;
if(QueueIsFull(pq))
return false;
pnew = (Node *)malloc(sizeof (Node));
if(pnew == NULL)
{
fprintf(stderr, "Unable to allocate memory! \n");
exit(1);
}
CopyToNode (item,pnew);
pnew ->next =NULL;
if(QueueIsEmpty(pq))
pq ->front = pnew;
else
pq ->rear->next = pnew;
pq -> rear = pnew;
pq -> items++;
return true;
}
bool DeQueue(Item * pitem, Queue *pq)
{
Node *pt;
if(QueueIsEmpty(pq))
return false;
CopyToItem(pq -> front,pitem);
pt = pq ->front;
pq -> front = pq ->front->next;
free(pt);
pq ->items --;
if(pq -> items ==0)
pq ->rear = NULL;
return true;
}
void EmptyTheQueue (Queue *pq)
{
Item dummy;
while (!QueueIsEmpty(pq))
DeQueue(&dummy,pq);
}
static void CopyToNode(Item item,Node *pn)
{
pn ->item = item;
}
static void CopyToItem(Node *pn, Item *pi)
{
*pi = pn ->item;
}
USE.c 源代码
#include <stdlib.h>
#include <stdio.h>
#include <mcheck.h>
#include "queue.h"
int main()
{
Queue line;
Item temp;
char ch;
mtrace();
InitializeQueue(&line);
puts("testing the queue .Type a to add a value");
puts("type d to delete a value , and type q to quit");
while((ch = getchar()) != 'q')
{
if(ch != 'a' && ch != 'd')
continue;
if(ch == 'a')
{
printf("Integer to add: ");
scanf("%d",&temp);
if(!QueueIsFull(&line))
{
printf("putting %d into queue \n", temp);
EnQueue(temp, &line);
}
else
puts("Queue is full");
}
else
{
if (QueueIsEmpty(&line))
puts ("Nothing to delete");
else
{
DeQueue(&temp, &line);
printf("Removeing %d from queue \n",temp);
}
}
printf("%d items in queue \n",QueueItemCount(&line));
puts("Type a to add . d to delete, q to quit:");
}
EmptyTheQueue(&line);
puts("Bye!");
return 0;
}
-
转载 单元测试(提升篇)
2008-11-25 10:34:05
单元测试(提升篇)
一、可测试性设计1. 接口依赖
这是最重要的一点,因为这可以使得我们很容易的针对一个接口实现Mock对象,模拟/替换实际对象会变的很容易。达到使一个被测对象处于一个孤立环境的测试要求。
这里,ClassA依赖于ClassB的具体实现,TestCase根本无法独立于ClassB对ClassA进行独立测试。因此,我们将ClassA改为依赖于接口B_Inf。这样,可以很容易的实现一个Mock_B替换ClassB去ClassA进行孤立测试。
2. 依赖注入
一个方法对外部类的依赖,应该是可注入的。即可以通过构造方法、get/set方法的方式在外部将依赖关系注入。事实上,这也为我们在测试用例中替换待测类的依赖对象提供了机会。不应该出现在方法内部新建对象使用的情况。
3. 降低耦合度
待测类应与最少的类耦合,即最小交互原则。特别是要减少与那些离了具体环境就不能运行的类的耦合。可以通过门面模式等对外部调用进行隔离。
5.AOP
面向切面编程。给我们提供的启示是,将真正需要测的逻辑分离出来。摆脱那些无意义且简单重复的代码对测试的干扰。
6. 明确的契约
方法一定要有明确清晰的输入/输出。建议在方法的注释描述中,分三段“描述”“前置条件”“后置条件”。
标题搜索
我的存档
数据统计
- 访问量: 40851
- 日志数: 50
- 建立时间: 2008-08-21
- 更新时间: 2011-02-11