• 软件开发实践:我的单元测试历程--这是一根不错的拐棍


    其实,最开始我是不做单元测试的。想想有那么多的代码,那么多的逻辑,要写多少单元测试函数。真正能实现百分百(更百分之九十)的代码覆盖率吗?想想就不可能,那得写多少代码啊!有用吗?而且如果代码重构,以前的单元测试函数岂不废了?

    1最初对单元测试的思考

    让我第一次认真思考单元测试,是有一次,软件通过了功能测试,自己又想重构代码的时候。

    如何保证代码重构后,不引入新问题呢?这时候想到了单元测试。

    1.1 缘由

    要是某个模块的接口(注意:本文中的接口是指外部可访问的函数或变量-变量非常不推荐,而不是c#和java中的interface)是明确的,又有单元测试做保证,不就可以放心重构模块内部的代码了吗?

    1.2 接口与覆盖率的思考

    这时,我意识到,接口很重要,有好的接口,代码就有了边界,在接口不便的前提下,边界内的代码可以随意动。

    也认识到,单元测试可能没有想象的那么多。

    如果接口是内部接口(不作为库使用),那么只要调用的地方覆盖到了也就行了。

    我还意识到,单元测试不能注重代码覆盖率,而应该注重逻辑覆盖率或功能覆盖率。

    写单元测试不能深入到代码中,而是应该站在接口的角度,考虑有多少种调用情况。从这个角度看,单元测试应该是黑盒测试。有多少人被代码覆盖率弄得晕头转向,可叹!

    如果代码习惯比较好,没有无效代码,覆盖了完整了逻辑,代码也就基本覆盖了。所以整洁的代码,走心的编码是必不可少的。

    后来我了解到,很多人认识单元测试,都是从支撑重构角度去思考的。所以说,重构是很重要的,至少它会引领你去思考单元测试。

    这里有个误区,如果不认真下手写单元测试是认识不到。那就是很多人认为可以先编码,编码完成后,甚至到重构时再写单元测试也可以。持这样观点的人,应该没有真真正正写过单元测试,或认真思考过单元测试。

    其实,这里也有另一个引人思考的问题:我们应该什么时候重构代码?显然过了功能测试后再重构代码,一定意义上来说,有点晚。

    1.3 单元测试与开发时间

    我还感觉到,单元测试可能会降低我们们的开发时间.

    拿web测试为例(此处不是说页面修改),我们在修改了代码,需要编译,发布,打开网页去验证我们的修改,有时还需要登录,打到位置再测试,如果页面有缓存,则要处理缓存。这多麻烦,容易出错,用时还长,很多还是重复的步骤。如果有单元测试,按一个键就搞定了(这是我当时的想象)。

    在认识到单测试有用,测试函数可能没有想象中多的时候,我就开始有意去做单元测试。

    2 对单元测试的探寻

    2.1 第一次接触单元测试

    在刚开始参加工作时,曾用junit写过一些单元测试.

    那时对单元测试现解不深,而且做的是对日外包项目。该项目刻板到每个函数都要写伪代码,单元测试能写成什么样子可想而知了。所以这次基本没得到多少有益的经验。

    2.2  寻找测试框架

    由于我在用c++,不知道还有gtest,nggtest框架,也许那时还没出来,或者名气还不大,再或者我找东西的方式有问题,反正我没找到一个合适的框架。我为此浪费了不少时间。其实,我们完全不需要执著于什么框架。这也显示了当时我不知道单元测试怎么做。

    及至后来看到一个开源项目live555(或者是其它项目,记不清了)。它的单元测试就非常简单,直接printf出结果,也没有自动对比正确性。我忽然意识到,什么形式框架根本不重要,重要的是我们想要的是什么。

    于是,我开始不用框架,自己写一些简单的单元测试程序。由于单元测试函数共同点太多,例如初始化,判断,统一执行等,很快我演化出了自己的测试框架,这里不再详述。

    2.3 单元测试的边界

    最开始写单元测试时,写的并不好。主要是分层分的不好。经常一个测试跨多个层,甚至连通讯软件也用上了,例如ActiveMQ。

    一度导致我苦恼很久:通讯层怎么测,数据库交互怎么测?

    后来才想明白:ActiveMQ和数据库与测试有什么相关,都是第三方的东西,去测第三方东西干嘛,就当成它们都是正确的就行了。

    我们主要目标测自已的逻辑是否正确,至于数据从哪来,到哪去,只要数据正确,可以默认都是好的。

    由于当时不知道mock,在这一个块花的工夫还真不少,例如自己重新写一个被调用的边界函数,里面提供数据,或对数据进行判断。

    3 单元测试的一些思考

    3.1 单元测试有利于降低代码的耦合度

    一直以为写自己写代码很谨慎,对待接口函数很重视,写了单元测试后,才发觉做的也还是不够。

    记得有一次写服务程序,周期运行。在函数中用到了系统时间,后来发现不好测。因为我们无法控制系统时间,所以每次要等一阵才能运行一次测试,很不方便。

    这使我意识到这里对系统有了依赖,应该去除这个依赖,于是就将系统时间作为接口参数传进去,测试就方便多了。可以传入想测的时间,进行测试。后来了解到mock,才知道可以mock系统时间,但我总觉得去除系统耦合性是很有必要的。

    3.2单元测试与重构

    一直在思考,什么时候做重构最合适,功能测试完成后,显然不是适合的时机。因为一改动,可能造成功能测试白测了。

    我一度认为,在完成几个类时,回头看看有没有共同点,如果有,则需重构。这时是个合适的时间点。又认为一个类或源文件完成后,适合重构,修改一下命名,添加一下注释,拆分一下长函数。

    写单元测试后才发现,重构适合在一个对外接口函数完成后,做单元测试的时候。 这时候,可以通过单元测试验证接口设计是否合理,逻辑是否正确,分层是否合适。

    3.3什么时候开始写单元测试

    什么时候开始写单元测试好呢?

    TDD说写代码之前先写单元测试,我没有尝试过。

    在编码时, 我不想让单元测试打段我编码时连贯的思路,所以我选在第一个对外接口函数完成时。

    当然,也可以选在想编译一下,看看是否能编译通过的时候。

    但是千万别选在所有接口函数都写完后,因为这时很可能你的代码由于耦合度太大或分层不好,已不具备可测性;还可能由于感觉测试代码量太大,放弃写单元测试。

    3.4 单元测试代码量大吗?

    这里就说到测试代码量,很多人不写单元测试,就是觉得单元测试代码量太大,性价比不高。

    其实真正写单元测试就会发现,单元测试量不大,一个接口函数最多也就五六个测试用例,再多就需要思考函数设计、定位是否合理了。

    我的实践情况是:单元测试用例就四五个,睛着函数看半天,需不需要更多的测试函数呢?结果发现,没有,就这些。我经常有这种体验。

    这些测试函数一般不会太长,同一接口函数的测试函数应该比较类似。

    3.5单元测试占用时间多吗?

    很多人觉很写单元测试很耗时,会浪费大量时间,其实不然。

    第一,  单元测试的同时,把重构给做了,节省了部分重构的时间。

    第二,  单元测试早编译,早发现问题,处理问题也容易。

    第三,  单元测试利于调试,我们写代码,大半时间在调试,调试时搭建环境需要不少时间,再执行一些重复的步骤进行调试,有时候需要重复多遍。

    而单元测试,只需要运行一下就可以了。

    第四,  还有,写一个程序,自然希望快速调用一下,看看效果,这是一个比较自然的过程,单元测试正好可以满足我们这个需求。

    3.6单元测试难写吗?

    单元测试并不难写,我写单元测试的思路是,写最少的代码,完成我想要测试的功能即可。感觉写单元测试就像写一篇有思路时的作文,还是很顺畅的。

    3.7 mock

    mock是我最近才了解到的技术,这个技术非常有用。

    以前写单元测试,遇到调用外部接口时,会自己实现一个版本,并在单元测试中以自己实现的版本替换真正的外部实现,有了mock后,我们就不需要这样做了,只需要简单调用mock就行了,而且mock可以做得更好,更多。

    例如,有个定时器或线程,要是没有mock,就不好测,除非将线程逻辑函数写为公用的,有了mock就方便多了。只是mock的语法稍微复杂一些,需要学习。另,用mock的测试用例可读性会差一些。

    3.8 单元测试在编码时作用明显

    我现在的习惯是,如果新功能,不管项目要求不要求写单元测试,我自己都会写单元测试,即使不会提交到代码库中。

    不写单元测试,就像登山没有拐棍,不是不行,但是总觉得不方便。

    编码完成后,我就对单元测试不怎么关心了,这习惯不好。这也和自己最初认为的“单元测试是为重构保驾护”不一致。

    我觉得单元测试真的在编码阶段起的作用比重构时要大,或者说在编码时,我己进行了大部分的重构。

    4 单元测试就是一根登山杖

    最后我想说的是,单元测试很有用,但不是必须的。这只不过是一种比较好的实践罢了。

    不用为此纠结。如果你想用,建议立即下手使用,多想多做,自然能体会到它的好处。

    没想通,没兴趣,不做单元测试也没什么,毕竟很多时候我们不写单元测试也这么过来了。

    只是于我个人每当遇到难解的bug,我会感叹,当初要是有单元测试多好。

     

  • 相关阅读:
    Java:XML篇,使用SAX写入XML数据
    Solr4:Solr查询结果JSP分页显示(每次查询只返回指定记录数)
    Java:JDBC篇,Connection连接至几种常用数据库(Oracle,MySQL,Access,SQL Server)
    单点登陆原理
    sqlldr自定义函数调用
    osworkflow使用jdbc如何不用配置数据源。
    IHttpHandler使用session
    java代码生成器的的实现
    db2 通用分页存储过程
    复写page的Render方法
  • 原文地址:https://www.cnblogs.com/Rong-/p/7648918.html
Copyright © 2020-2023  润新知