单元测试并不是一门很复杂的技术,我相信很多程序员在刚开始工作的时候也都对单元测试有了基本的掌握。但是,最近我在实际工作中发现,很多时候单元测试并没有发挥其应有的作用,更多的时候成了一种提高代码测试覆盖率的手段。下面我就谈谈我对单元测试的看法以及我的一些经验。
单元测试的意义
这是一个很多人都知道答案的问题,但我还是要多唠叨几句。单元测试不仅仅是一种测试的手段,它更是一种设计方式,是一种保障。在 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 就是这么一个非主流的技术)
分享到:
相关推荐
单元测试框架如JUnit和Mockito,以及集成测试工具如Selenium,帮助程序员编写可验证的代码并发现潜在问题。 最后,Java程序员需要关注最新的技术趋势和框架,例如响应式编程(Reactive Programming)和微服务架构,...
本教程“Verilog的那些事儿”是针对FPGA开发的学习资料,通过实例深入浅出地讲解Verilog的设计原理和实践技巧。 1. **Verilog基础概念** - Verilog HDL(Hardware Description Language)是一种文本形式的语言,...
《FPGA那些事儿--Modelsim仿真技巧REV6.0》是一本深入探讨FPGA设计与Modelsim仿真技术的专业书籍。FPGA(Field-Programmable Gate Array)是可编程逻辑器件,广泛应用于数字系统设计中,它允许用户根据需求自定义...
本压缩包“大码包 VerilogHDL那些事儿-整合篇 对应例程”提供了与Verilog HDL相关的实践案例,帮助学习者深入理解和掌握这一强大的设计工具。 首先,让我们来了解一下Verilog HDL的基础概念。Verilog是一种行为、...
5. **产品测试与优化**:测试是确保产品质量的重要环节,包括单元测试、集成测试、系统测试和用户验收测试。报告会讲述如何构建有效的测试策略,以及如何利用数据分析来优化产品性能和用户体验。 6. **产品上市与...
在完成《VerilogHDL那些事儿-建模篇》之后,本书进一步探讨了时序在Verilog HDL设计中的重要性和应用方法。通过深入理解时钟信号的作用、“步骤”的概念以及如何进行有效的综合与仿真,设计师可以更好地应对复杂的...
5. **聚合报告**:Maven可以生成多种项目报告,如代码覆盖率、Javadoc、单元测试结果等。 ### 学习资源与进阶 要深入了解Maven,你可以参考以下资源: - Maven官方文档:...
2. **可测试性**:良好的代码应该易于进行单元测试和集成测试。 3. **可读性**:代码应该简洁明了,便于他人理解。 4. **可理解性**:即使是初学者也应该能够轻松读懂代码。 5. **避免盲目跟随潮流**:不要仅仅因为...
代码质量的保证不仅需要明确的测试覆盖率和单元测试覆盖率,还需要内嵌的质量保证机制,如代码评审和静态代码检查。此外,质量门禁的执行是自动化测试中的重要组成部分,它能够确保质量标准被满足。 四、DevOps推广...
作者提出了一种名为“低级建模”的技巧,这或许指的是从最基本的逻辑单元开始构建,逐步积累经验,形成自己的一套建模习惯。这种方法强调从基础做起,注重实践,通过大量实验和测试来优化和验证自己的建模方法。 ##...
6. **测试**:使用Xcode的内置测试工具进行单元测试和UI测试,确保应用在不同设备和操作系统版本上的功能和性能。 7. **App ID与Provisioning Profile**:每个应用都有一个唯一的App ID,而Provisioning Profile...
A/B 实验是一种数据驱动的方法,广泛应用于互联网行业,用于测试和优化产品、服务或营销策略。通过对比实验组(A组)和对照组(B组)的表现,我们可以量化不同变体的效果,从而做出更科学的决策。以下是关于A/B实验...
一些CTF竞赛会搭建模拟的工控环境,供参赛者进行安全测试和渗透实验。 工控安全的挑战包括对生产流程的程序进行修改,造成系统停机,获取私人专利数据,勒索钱财,甚至是国家级的攻击行为。由于工控系统对于国家...
Java程序员需要熟练掌握JUnit等测试框架,能够为每个功能模块编写相应的单元测试用例,以验证代码的正确性和稳定性。 #### 代码审查 团队协作中,代码审查是必不可少的一环。通过代码审查,可以发现潜在的问题、...
- **PL(Programmable Logic)**:这部分由可编程的FPGA逻辑单元组成,用于实现用户自定义的硬件加速功能。 ##### PS和PL互联技术 PS和PL之间的通信是通过AXI总线实现的,这使得数据可以在两个部分之间高效地传输...
6. 情绪管理:歌词“那都不是事儿”表达了一种积极面对生活的心态,即学会控制情绪,排解不良情绪,保持乐观健康的心态。这对青少年的成长至关重要。 7-8. 青春期心理变化:王芳的变化是青春期常见的现象,包括嗓音...
14. **测试**:单元测试和集成测试是保证代码质量的重要手段,JUnit是Java常用的单元测试框架,而Mockito则可以帮助模拟对象进行测试。 15. **持续集成/持续部署(CI/CD)**:了解Jenkins、Git等工具,实现自动化构建...
采用单元测试、集成测试和端到端测试相结合的方式,可以确保软件的质量。同时,持续集成和持续部署(CI/CD)能快速发现并修复错误,确保软件的稳定性和可靠性。 总结来说,项目管理涵盖了源码管理、工具应用、文档...