文章目录
  1. 1. 基本概念
    1. 1.1. 单元测试
      1. 1.1.1. 如何进行Mock
    2. 1.2. 集成测试
    3. 1.3. 功能测试
  2. 2. 测试方法
    1. 2.1. Behavior-driven development(BDD)
      1. 2.1.1. Cucumber
        1. 2.1.1.1. 业务描述
        2. 2.1.1.2. 步骤实现
  3. 3. 测试框架
    1. 3.1. Test Runner
    2. 3.2. BDD
    3. 3.3. Mock库
    4. 3.4. 断言&匹配库

基本概念

单元测试

一些单元测试中设计的基本概念

  • Stub 指的是以可控的方式对对象方法进行模拟,使其按照预期的行为返回结果或抛出异常等的过程。
  • Mock Object 整个对象都是模拟对象,所有方法在模拟之前进行调用都是空方法。
  • Spy Object 对真实对象的部分方法进行模拟,除了模拟的方法外调用的都是真实的方法。

单元测试的主体对象是方法,目的是测试目标方法的执行是否符合预期的行为,包括执行路径、返回值等。实施单元测试的主要目标是应用的POJOs。测试这些对象应该通过new来进行初始化,而不是通过从容器中获取的对象。目标方法中调用的过程中会调用其他的对象方法,可能基于下面的这些原因

  • 真实对象的行为是不确定的(例如,当前的时间或当前的温度);
  • 真实对象很难搭建起来;
  • 真实对象的行为很难触发(例如,网络错误);
  • 真实对象速度很慢(例如,一个完整的数据库,在测试之前可能需要初始化);
  • 真实的对象是用户界面,或包括用户界面在内;
  • 真实的对象使用了回调机制;
  • 真实对象可能还不存在;
  • 真实对象可能包含不能用作测试(而不是为实际工作)的信息和方法。

不能够或者不应该使用真实的对象方法。使用Mock对象,可以避免其他方法执行结果对目标方法执行的干扰,同时能够更好的控制方法的执行路径。

实施单元测试的好处不仅仅在于它能保证代码的正确性,更大的好处在于它的高效,因为它是通过new初始化的,调用的对象也可以进行mock,并不依赖于难以控制的外部环境。另外的好处是可以间接地提高代码的品质要求,如果你想实施单元测试,那么你得要求你的类尽可能的是一个POJO,代码应该应有较好的分层结构以及松散的耦合,一段品质很差的代码要实施单元测试是非常苦难的。

如何进行Mock

下面是基本的mock准则

  • 不要什么都mock
  • 不要mock不是你的类
  • 不要mock值对象

针对大多数的mock框架,使用mock对象的步骤大致是一致的

  1. 创建mock对象
  2. 录制需要mock的方法行为
  3. 调用mock对象
  4. 验证

下面是Mocktio的示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// you can mock concrete classes, not only interfaces
LinkedList mockedList = mock(LinkedList.class);
// stubbing appears before the actual execution
when(mockedList.get(0)).thenReturn("first");
// the following prints "first"
System.out.println(mockedList.get(0));
// the following prints "null" because get(999) was not stubbed
System.out.println(mockedList.get(999));
// virify get(0) is called
verify(mockedList.get).get(0);

集成测试

集成测试主要针对是测试多个组件组合在一起工作的情况。 例如Repository接口和Spring Data JPA集成的情况下,生成的查询接口测试;Web框架和Controller集成的情况下,请求参数的绑定、转换、验证是否正常;和消息中间件集成时,消息的收发是否正常等等。

功能测试

功能测试有时候也叫E2E测试,功能测试以系统的功能点为测试对象。以Web应用为例,功能测试的主要应该是测试页面的功能是否正常,例如测试

  • 请求是否成功
  • 重定向是否正确
  • 用户是否成功授权
  • 页面的元素是否正常显示
  • 页面的表单的验证是否正确,提交后的跳转是否正确

功能测试最常用的工具Selenium。当然有些框架也提供应用内的功能测试,例如Rails的Functional Test,但是这些测试不属于E2E测试的范畴,因为他们是基于容器内的测试,而不是完全独立的端到端的测试。

测试逻辑

1
2
3
4
5
6
CreateMessagePage page = CreateMessagePage.to(driver);
ViewMessagePage viewMessagePage =
page.createMessage(ViewMessagePage.class, expectedSummary, expectedText);
assertThat(viewMessagePage.getMessage()).isEqualTo(expectedMessage);
assertThat(viewMessagePage.getSuccess()).isEqualTo("Successfully created a new message");

测试方法

Behavior-driven development(BDD)

行为驱动开发是一种敏捷软件开发的技术,它鼓励软件项目中的开发者、QA和非技术人员或商业参与者之间的协作。 BDD是一种开发方式,但是它和测试紧密相关。BDD用它的DSL来描述系统的功能的,测试用例则则相当于对功能描述一个实现。

Cucumber

Cucumber是一个BDD的框架,它支持各种语言的实现。

业务描述

Cucumber使用Gherkin语言来作为DSL来对业务规则进行描述,下面是一个Gherkin的文档

1
2
3
4
5
6
7
Feature: Refund item
Scenario: Jeff returns a faulty microwave
Given Jeff has bought a microwave for $100
And he has a receipt
When he returns the microwave
Then Jeff should be refunded $100

一个Scenario指的是一个业务规则的演示、描述。其中包括Give、And、When、Then等多个Step。

上面的文档就是非技术人员或商业参与者需要做的,使用DSL语言对系统功能进行描述。

步骤实现

开发人员则需要对上面的文档中的描述进行实现,调用实际的功能代码,以达到测试代码的目的。

例如,针对下面的Step

1
2
Scenario: Some cukes
Given I have 48 cukes in my belly

进行实现

1
2
3
4
@Given("I have (\\d+) cukes in my belly")
public void I_have_cukes_in_my_bellt(int cukes) {
System.out.format("Cukes: %n\n", cukes)
}

实现方法是应该调用实际的业务代码,对于Given来说,应该是类似setUp来初始化测试上下文环境的。

测试框架

下面主要介绍下各个平台中较为广泛的使用的测试框架

Test Runner

Test Runner是用来启动测试,调用测试代码,反馈测试结果的。它本身可能作为一个测试框架还包括了断言、Mock等其他额外的功能

  • Karama 它作为JS的Test Runner,仅负责测试的运行。它是一个Node.js的模块,能够在本地启动浏览器执行测试用例,并将测试结果输出到本地控制台。
  • Junit Java平台的测试框架,它是一个完整的测试框架,除了负责运行测试用例,它还包括测试方法定义,断言的书写等功能。
  • XCTest 从Xcode5,使用XCtest作为OS X和iOS平台的完整测试框架。

BDD

即便开发团队不使用完整的BDD框架进行开发,使用BDD风格的测试用例描述方式,仍然有诸多的有点,测试用例具有良好的可读性。

下面是一些BDD测试框架,其中一部分虽然是测试框架,但也创建拥有功能描述文档的功能

  • Jasmine 基于JS的完整BDD测试框架,包括断言、mock的功能。
  • Spock 基于Groovy的BDD测试框架,有用自己特有的断言方式,也支持使用groovy-test的断言。
  • Specta 基于Object-C和Cocoa的BDD测试框架,它不提供断言、mock的功能。
  • Kiwi iOS平台的完整BDD测试框架,包括了断言、mock的功能。

Mock库

除了针对对象方法的mock库,还有一些实现了类似mock功能的库。

  • [OHHTTPStubs] iOS平台的HTTP请求的Stub库。它可以Stub所有通过NSURLConnection接口进行的请求,模拟数据的返回,模拟网速慢的情况的请求等。 除了可以用于更高效的测试外,还可以用于开发阶段,API等尚未完成时的接口模拟等。

断言&匹配库

匹配库本身并不是测试必要的一部分,但是有了匹配库可以让断言的书写更为简洁和高效

  • Jasmine Jasmine包括了断言的功能
  • AssertJ 具有fluent API的Java断言库
  • Hamcrest assertThat风格Java断言库,它同时是个匹配库,拥有很多非常实用的匹配器,能够和AssertJ集成
  • Expecta 基于Object-C和Cocoa的断言库,同时也是Specta的兄弟库
文章目录
  1. 1. 基本概念
    1. 1.1. 单元测试
      1. 1.1.1. 如何进行Mock
    2. 1.2. 集成测试
    3. 1.3. 功能测试
  2. 2. 测试方法
    1. 2.1. Behavior-driven development(BDD)
      1. 2.1.1. Cucumber
        1. 2.1.1.1. 业务描述
        2. 2.1.1.2. 步骤实现
  3. 3. 测试框架
    1. 3.1. Test Runner
    2. 3.2. BDD
    3. 3.3. Mock库
    4. 3.4. 断言&匹配库
Fork me on GitHub