1、单元测试,快速的编程反馈,软件开发的困难,软件总是要修改的 添加新特性 Fix bug 优化 重构,软件开发的困难,修改软件是危险的举动 我们如何得知已经正确的完成了修改? 我们如何得知没有破坏任何(既有的)东西?,软件开发的困难,当前我们如何测试修改 编译 启动服务器 启动一个以上的客户端 鼠标操作 看看效果 重复这些步骤,软件开发的困难,这种测试的缺点 不能立即获得反馈 时间漫长 运行次数不足 甚至不测试,软件开发的困难,这种测试的外号 编辑并祈祷,单元测试,概念 由一组独立的测试构成,其中每个测试针对软件中的一个单独的原子行为单元 并非检查一簇类是否能够合作良好,而是检查单个的对象行为是
2、否正确,单元测试,传说中的例子,被测试的对象非常独立,TEST( TestAdd ) Math math;int result = math.Add( 11 , 12 );CHECK_EQUAL( 23 , result ); ,单元测试,传说中的过程 实例化被测试对象 提供测试数据 调用被测试的方法 验证测试结果,单元测试,实际情况1内存访问异常,ItemMngS无法在测试中实例化,TEST( TestItemMng ) CItemMngS mng; ,单元测试,实际情况2内存访问异常,无法调用被测试的方法,TEST( TestBehit ) CServerChaPlayer player;
3、CServerChaPlayer target;target.Behit( player ); ,单元测试,实际情况3函数没有返回值,不知道如何验证结果,TEST( TestAddInvite ) CGroupUnitS unit;CServerChaPlayer player1 , player2;unit.AddInvite( / how to check ? ,单元测试,实际的情况 理想是美好的,世界是残酷的 软件由对象的相互协作来实现功能,对象之间充斥着必不可少的依赖 软件的运行依赖于数据库,网络和文件系统 多数函数的并没有返回值来验证行为,单元测试,比如 CServerCha依赖各种
4、CXXMng CXXMng之间相互依赖 Auto_PlayerData是数据库的接口,单元测试,依赖的坏处 无法将某个类从软件中隔离出来 无法轻易的安置单元测试,单元测试,不经意的转变成集成测试 我们可以将被依赖的类在测试中实例化 被依赖的类肯定也在依赖别的类 一环扣一环,于是我们把整个软件的大部分类都牵扯到一次测试中 这个单元测试变得笨重 这已经不是单元测试了!,解依赖,有依赖,就要解依赖 在被测试的代码中,依赖是少量的 解依赖就是将无法放入测试中的小段代码从整块代码中分离出来的行为 解依赖就是构造接缝的过程,解依赖,什么是接缝? 接缝是指程序中的一些特殊的点,在这些点上你无需作任何修改就可
5、以达到改动程序的目的 如果能够将接缝处的行为取代掉,我们就等于有选择的排除了某些依赖 我们还可以将被依赖方替换为其他代码,以此感知测试代码对被依赖方的要求和影响,解依赖,其实接缝就是间接层 接缝的基本类型 虚函数 指针:函数指针,全局变量,参数,解依赖,虚函数 虚函数是接缝的根本 在不改变软件的前提下,虚函数的行为可以在测试中的被派生类所改变,解依赖,code,解依赖,伪对象 由于虚函数的存在,在测试中的派生类可以改变原来的行为 在测试中用于伪装成被测试类的合作者的对象,称为伪对象 伪对象是后面介绍的解依赖技术的根本,解依赖,全局变量 实例化的全局对象是恶劣的,如:CPlayerMng g_p
6、layerMng; 隐性初始化的单件是恶劣的,如:CCommonTimerMng:Instance(); 对象指针是一个接缝,可以在测试中替换指针实际指向的对象 改变对象的行为需要虚函数,解依赖,code,解依赖,函数指针 全局函数是不可替换的,如:G_GetPlayerUnit 但是函数指针可以在测试中替换为其他签名相同的函数,解依赖,code,解依赖,参数指针 参数对象指针和全局对象指针一样,可以在测试中替换实际指向的对象 行为的改变依赖于虚函数,解依赖,code,解依赖,完全没有找到接缝,怎么办? 没有接缝就创造接缝,解依赖,创建接缝的方法 提取并重写调用 设置并替换 提取并重写获取方法
7、 提取并重写工厂方法,解依赖,提取并重写调用 被测试的代码中,委托了其他的对象做某件事情 给被测试对象新添加一个虚函数,封装这个委托 在测试中用派生类覆盖这个虚函数,解依赖,code,解依赖,设置并替换 对被依赖的对象或者单件提供一个设置方法 在测试中,用设置方法改变他们,解依赖,code,解依赖,提取并重写获取方法 在被测试的代码中,委托了其他对象做某件事情 给被测试类添加一个获取委托对象的虚函数 在测试中用派生类覆盖这个虚函数,解依赖,code,解依赖,提取并重写工厂方法 构造函数包含太多依赖,以至于无法实例化被测试的类 将依赖的代码提取到一个虚函数中 在测试中覆盖该虚函数,解依赖,cod
8、e,解依赖,创建接缝的更高级的方法 参数包装器 方法对象,解依赖,参数包装器 有些类是无法修改的,final的 有些类几乎无法实例化,解依赖也很费劲 对这样的类,总是可以用wrapper将它包起来 将实际代码中对该类的依赖换为对wrapper的依赖 Wrapper有一套抽象的接口,解依赖,code,解依赖,方法对象 有一些类十分巨大 被测试的方法又臭又长 创建一个新类,该类只有一个方法 将被测试的代码放到新类的唯一方法中,解依赖,code,解依赖,总而言之,解依赖不是使用其中的某项技术,而是这些技术的综合运用 虚函数是必须的 解依赖是可以做到的 解依赖是一种手术,手术可能会留下疤痕,但是可以治
9、愈疾病,接下来,解依赖之后,也许就可以像传说中那样编写测试了 真实的单元测试中,被测试类总是要在测试中被派生的,而且少不了其他伪对象的协助,接下来,code,如何开始,当前的项目完全没有单元测试 动手吧,但不要试图一下子为所有的代码都建立单元测试 蚕食,每次要进行修改,都试试将修改的部分放到测试下 就好像海里不断生长的岛屿,最终会变成大陆,如何开始,真的要开始吗?给我一个理由 速度,一眨眼就穿越了 包含反馈,能够定位问题的所在 自动化 设备无关(数据库,网络,文件系统),如何开始,步骤 确定修改点 确定测试点 解依赖 编写测试 修改和重构,如何开始,工具 CppUnitLite 由CppUni
10、t的作者所编写,一改复杂的使用方式 TEST宏,CHECK_EQUAL宏,CHECK宏 TEST_F宏(fixture),如何开始,code,TEST( 测试名 ) 测试代码CHECK_EQUAL( 预计的结果,代码计算的结果 );CHECK( 条件 ); ,如何开始,经验 将被测试的软件编译成lib/dll,这样可以新建一个独立的solution来布置测试代码,并且不会对原来的软件造成影响。如:TTYGSZ_Lib 类的成员变量也许protected会好一点,因为在测试中派生类需要访问它们,来验证结果,如何开始,经验 可以放弃测试MayXXX的方法。因为测试他们需要准备很多伪对象,而他们实际做的工作仅仅是if return而已,更多的考虑,有种单元测试相关的软件开发技术,叫做测试驱动开发,可以考虑作为下个项目的开发方案之一 单元测试很好很强大,但不是银弹 Code review对交流和成长更有帮助,阅读,修改代码的艺术,