`
SSailYang
  • 浏览: 307903 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

单元测试那些事儿

阅读更多
单元测试并不是一门很复杂的技术,我相信很多程序员在刚开始工作的时候也都对单元测试有了基本的掌握。但是,最近我在实际工作中发现,很多时候单元测试并没有发挥其应有的作用,更多的时候成了一种提高代码测试覆盖率的手段。下面我就谈谈我对单元测试的看法以及我的一些经验。

单元测试的意义
这是一个很多人都知道答案的问题,但我还是要多唠叨几句。单元测试不仅仅是一种测试的手段,它更是一种设计方式,是一种保障。在 TDD 中,单元测试可以避免过度设计。而在代码重构中,单元测试同其它自动化测试一道,保障的重构可以顺利进行。

单元测试测什么
简单来说,单元测试测试的是被测试单元(通常是一个方法)所承诺的功能。单元测试通常是白盒测试(后面会提到例外的情况),单元测试不应关注被测方法的内部实现,而应该检查方法的返回值、方法对变量状态的改变、方法所做的重要动作(例如发送消息的内容、写文件的内容、数据库操作的结果,等等)。

单元测试的范围
一个项目中不是所有的代码都需要单元测试覆盖,执意对代码覆盖率高追求是不正确的。但这就引出一个问题,哪些代码更需要单元测试,哪些代码则不是很需要。单元测试的一个重要意义就是保证代码的变动不会破坏其应有的功能,所以,变动的可能相对较小的代码,其代码需要单元测试的意义也就相对较小。例如,如果你的 DAL (Data Access Layer) 采用了 Hibernate,那增删查改的这些方法边不需要单元测试的。因为这些代码基本上就是对一个框架 API 的封装而已。

单元测试的形式
一个单元测试大致可以分成三部分,其实这也是很多测试的形式,即 Given-When-Then。首先是给出前置条件,例如这个方法的入参是多少、这个方法所属实例的变量的状态、相关环境(文件、数据库)等的状态。然后是调用被测试的方法。最后是对结果的检查。

在想 Spock 等的 BDD 测试框架中,一个测试用例的形式如下:

import spock.lang.Specification;

class RomanCalculatorSpec extends Specification {
    def "I plus I should equal II"() {
        given: 
            def calculator = new RomanCalculator()
        when:
            def result = calculator.add("I", "I")
        then:
            result == "II"
    }
}


如果是使用 JUnit 或者 TestNG 这样的单元测试框架,我也建议用 Given-When-Then 的形式划分测试代码,这样代码的意图显得很清晰。

单元测试 与 Mock
单元测试主要是靠 xUnit 类的框架实现的,但是只用 xUnit 在很多情况下不能实现单元测试。因为在实际工作中,被测试的代码很多都是有环境依赖的。而这种依赖,在单元测试中通常是无法提供的。所以就需要用 Mock 框架来消除这种依赖。这是 Mock 框架所提供的主要功能之一。Mock 框架的另一个重要功能是它可以验证其所 Mock 的类或接口中的某个方法在测试过程中是否被调用。虽然这种做法不符合白盒测试的原则,但当你因为在单元测试中无法实现而 Mock 某个方法时,而这个方法又是十分关键的,通过 Mock 框架来检查这个方法是否按期望被调用也是可以的。例如,你的 UT 所测试的功能需要调用一个数据库相关的操作,这个操作十分重要,是你 UT 被测方法的所承诺的关键功能,但是这个操作偏偏不能在 UT 中直接执行,或者这么做起来很费劲。这时就可以用 Mock 框架来上场了。下面以 Mockito 为例简单说说 Mock 的形式。

List mockList = mock(List.class);
mockList.add("one");

verify(mockList).add("one");

(上面这段在 Mockito 项目主页上也有,比较简单,不解释)

如果想要检查被 Mock 的方法的入参是什么?这往往是很重要的。否则,入参不正确,怎么保证 Mock 的方法被正确调用了呢?

假设,你要测试的方法依赖 UserDAO,因为 UserDAO 依赖数据库环境(虽然现在有基于内存存储和基于文件存储的轻便关系型数据库,使得在一些场景下,基于数据库的方法可以在 UT 中直接被测试,但在一些情况下,例如基于某种数据库特殊语法的操作,存储过程的操作等等,这些还是很难直接在 UT 中被测试)

UserDAO:
Collection users = query(UserCriteria criteria);


你可会这样写 Mock

UserDao userDao = mock(UserDAO.class);
when(userDao.query(new UserCriteria())).thenReturn(users);// users 是你设定好的返回值


不幸的是,这样写是错的。即便是这样写

when(userDao.query(any(UserCriteria.class))).thenReturn(users);// users 是你设定好的返回值


这样写虽然可以正常运行,但是这样的测试是不可靠。因为不论 UserCriteria 里的参数是什么,你所 Mock 出来的方法返回的都是你期望的值。这显然是与现实不符的。这是就需要 Hamcrest 的 Matcher 登场了

Matcher matcher = new BaseMatcher() {
    @Override
    public boolean matches(Object o) {
        UserCriteria criteria = (UserCriteria) o;
        assert criteria.field1;
        assert criteria.field2;
        assert criteria.field3;
        ....
        return true;
    }
};

when(userDao.query(argThat(matcher))).thenReturn(users);// users


注意,这里的 assert 指的是 JUnit 的 assertEquals 等,或 assertThat (我更推荐)。当然,TestNG 的也是可以。但不要使用 Java 中的 assert 关键字。

这样,你就可以对你 Mock 的方法的入参做细致的检查了。

测试支持代码
一个项目的单元测试的代码通常都是直接针对项目主目录中代码的测试,但是一些情况下,你需要针对你所使用的技术开发一些代码以辅助你的单元测试。这些代码既不是你的项目功能实现,也没有测试任何一个方法。一个优秀的框架,尤其是那些非基础设施类的框架(基础设施类的框架如数据库相关的 Hibernate、iBatis 等),往往都提供了方便用户单元测试的辅助代码。例如,Spring Framework 有专门用户测试的子模块 spring-test,Spring MVC 也有帮助测试的类。Apache Camel 也有专门用于简化测试的子项目(Camel Test)。但如果不凑巧,你用的技术直接测试起来不方便,同时有没有相关的简化测试的类库,那就不要犹豫了,自己丰衣足食吧。(我在项目中用到的 Sip Servlet 就是这么一个非主流的技术)
分享到:
评论

相关推荐

    H3C《交换那些事儿》系列维护资料.rar

    01.《交换那些事儿》- 基础维护篇 - 流统 02.《交换那些事儿》- 基础维护篇 - 镜像 03.《交换那些事儿》- 基础维护篇 - IRF升级指导 04.《交换那些事儿》- 基础维护篇 - IRF替换指导 05.《交换那些事儿》- 基础...

    软件测试那些事儿

    目录: 一:软件测试-一般测试场景 二:软件性能测试常见问题“问”与“答” 三:软件安全性测试“Checklist” 四:移动APP测试中的非功能测试 分享方式:免费

    NIOSII那些事儿

    NIOSII那些事儿NIOSII那些事儿NIOSII那些事儿NIOSII那些事儿NIOSII那些事儿NIOSII那些事儿NIOSII那些事儿NIOSII那些事儿NIOSII那些事儿NIOSII那些事儿

    java那些事儿chm

    java那些事儿chm java那些事儿chm java那些事儿chm

    linux 那些事儿全集

    linux 那些事儿全集 linux 那些事儿全集 linux 那些事儿全集 linux 那些事儿全集 linux 那些事儿全集

    Linux那些事儿之全集

    导读.doc Linux那些事儿之我是Block层.pdf Linux那些事儿之我是EHCI主机控制器.pdf Linux那些事儿之我是Hub.pdf Linux那些事儿之我是USB_core.pdf Linux那些事儿之我是U盘.pdf等等 Linux那些事儿系列全在这里了

    LINUX\Linux那些事儿系列

    LINUX\Linux那些事儿系列 LINUX\Linux那些事儿系列 LINUX\Linux那些事儿系列

    Linux那些事儿

    《Linux那些事儿》分为9个部分。 Linux那些事儿之我是U盘 Linux那些事儿之我是Hub Linux那些事儿之我是USB Core Linux那些事儿之我是UHCI Linux那些事儿之我是EHCI控制器 Linux那些事儿之我是PCI Linux那些事儿之我...

    VerilogHDL那些事儿V3.0.pdf

    VerilogHDL那些事儿V3.0.pdf

    NIOSII那些事儿 Qsys版

    《NIOSII那些事儿》最新版,用Qsys进行布线。

    【代码】CSS那些事儿书中源码.rar

    【代码】CSS那些事儿书中源码 林小志写的非常全面。里面的例子源码很值得推荐学习。

    交互设计那些事儿

    交互设计那些事儿 PDF文档整理 为原网络文章“交互设计那些事儿一”和“交互设计那些事儿二”两篇文章的综合版本,不原下载的同学可到http://ucdchina.com/查看原始文章!

    H3C《交换那些事儿》系列维护资料【第2期】.rar

    25.《交换那些事儿》-技术专题篇-DRNI组网故障处理机制 26.《交换那些事儿》-技术专题篇-DRNI+VRRP及DRNI+VLAN双活组网配置介绍 27.《交换那些事儿》-技术专题篇-DRNI组网ARP及MAC表项同步机制介绍 28.《交换那些...

    Linux那些事儿系列.rar

    》包括《Linux那些事儿之我是Hub》、《Linux那些事儿之我是Sysfs》《Linux那些事儿之我是UHCI》、《Linux那些事儿之我是USB core》、《Linux那些事儿之我是U盘》,令人叹为观止的一个linux系列书籍。只能说,江山代...

    Linux那些事儿(linux内核写的很详细)

    Linux那些事儿 Linux那些事儿之我是Block层 Linux那些事儿之我是Sysfs Linux那些事儿之我是U盘

    《SDRAM那些事儿》第一季-轻松设计SDRAM控制器源码

    《SDRAM那些事儿》第一季-轻松设计SDRAM控制器《SDRAM那些事儿》第一季-轻松设计SDRAM控制器

    CSS那些事儿.pdf

    本书专注于CSS技巧实例的讲解,由浅入深地分析了CSS样式在布局时所需要理解的原理。放弃到处可见的基础知识、网络中能随意搜索到的hack技巧,侧重原理分析,拓展读者使用CSS布局的思维方式,通过本书的阅读读者将会...

    css那些事儿---css

    css那些事儿---csscss那些事儿---css

    Linux那些事儿1-9合集

    读过《linux那些事儿之我是U盘》的人,都知道其风格,我就不多说了。 导读: linux那些事儿之我是U盘 linux那些事儿之我是HUB linux那些事儿之我是USB Core linux那些事儿之我是UHCI Linux那些事儿之我是EHCI主机控制...

Global site tag (gtag.js) - Google Analytics