面向测试编程
什么是单元测试,什么是集成测试
单元测试是只测试一个特定单元的测试,如果你的测试需要启动多个层而不是只启动这个被测试单元,那它就是一个集成测试。
一种比较前沿的观点认为:访问代码、管理者代码、存储代码和业务代码里,只有第四种需要测试,其他的逻辑的正确性只要由顺序执行保证就行了。这这第四种测试,是不需要 mock 的,尽量使用 main 就能启动。这就要求把业务逻辑和输入输出解耦。和输入解耦比较简单,和输出解耦需要一定的巧思-把业务逻辑写成纯函数也许能达到这一目的。
而集成测试意味着我们要启动尽可能大的完整的程序,进行:
- 功能测试
- 验收测试
- 端到端测试
mock object
历史上的 mock object 是为了 peel out 整个 framework,让测试变轻而设计出来的。但如果 mock 的配置比较繁琐,则 mock 仍然很重。
当一个对象的依赖的行为很难定制而需要定制的时候,mock 对象就登场了。在历史上,软件工程的语言里,关于什么是 mock、stub、fake 和 stub 有漫长的争论,它们的共同点是都是真接口的假实现。
mockito 已经完成了一个 mock 的精确定义,stub 和 spy 都是 mock 的一些子行为,或者特定类型。stub 是Mockito.when(list.get(Mockito.anyInt())).thenReturn("hi");
;spy 是对真实对象进行部分 mock 的。
Case 分类
参考:
- What is the explicit difference between an edge case and a corner case?
- What are the difference between an edge case, a corner case, a base case and a boundary case?
edge case
An edge case is a problem or situation that occurs only at an extreme (maximum or minimum) operating parameter. For example, a stereo speaker might noticeably distort audio when played at its maximum rated volume, even in the absence of other extreme settings or conditions.
边缘情况是仅在极端(最大或最小)操作参数下发生的问题或情况。例如,立体声扬声器在以最大额定音量播放时可能会明显失真,即使没有其他极端设置或条件。
corner case
Corner case occurs outside of normal operating parameters, specifically when multiple environmental variables or conditions are simultaneously at extreme levels, even though each parameter is within the specified range for that parameter. (The “outside normal operating parameters” obviously means something like “outside typical combination of operating parameters”, not strictly “outside allowed operating parameters”. That is, you’re still within the valid parameter space, but near its corner.)
在工程中,极端情况 corner case(或病理情况 pathological case)涉及仅在正常操作参数之外发生的问题或情况——特别是当多个环境变量或条件同时处于极端水平时表现出来的问题或情况,即使每个参数都在该参数的指定的范围内。
boundary case
Boundary case occurs when one of inputs is at or just beyond maximum or minimum limits.
当输入之一处于或刚好超过最大或最小限制时,就会发生边界情况。
我们常说的 corner case,很大一部分是 boundary case。
base case
Base case is where Recursion ends.
基本情况是递归结束的地方。
developer-side test framework 搭建
集成测试
- 选择测试框架和测试插件。
- 确定分包:对于 gradle 而言,这一步最难。
- 建立基类,确定标准的初始化可运行框架的方法。
- 在基类的基础上,确定可插拔的 mock 的方法:
- 全局有可 ioc 的对象的时候,上下文依赖什么配置初始化。
- 局部有 mock 需求的时候,怎样替代 ioc 的对象。
- 如果我们需要使用 mock,有多少种标准化的录制 mock 的方法。待 mock 的服务有哪些:cv?nlp?db?cache?搜索引擎?
- 确定运行测试的范围,我们能够 mock 的模型数据有哪些?如:登录数据。
- 我们的测试运行在什么环境下,有哪些外部环境数据我们可以在运行时稳定读到,哪些是不能稳定读到的?
- 列出所有的 normal case,决定 case 的命名方法。
JUnit
JUnit 是事实上的单元测试框架标准,但事实上 JUnit is more than a unit test frame work, but a developer-side test framework。
JUnit 5 is modularized,composed of several modules: platform、vintage 和 jupiter。其中 The JUnit Platform serves as a foundation for launching testing frameworks on the JVM。在上面可以开发针对 JUnit 5 的 testframework,因为提供了专门的 TestEngine API。jupiter 是被重写的新 api,拥有新的 TestEngine 实现,而 vintage 兼容老版本的测试,拥有一个独特的 TestEngine 实现。
当代的 JUnit 已经变成了一个 annotation-driven 运行的 framework 了。其中全部的生命周期注解可以看这里。
Spring 与 Test
Unit Test
Mock Object
org.springframework.mock 下有 env、http、jndi、web 四个子包。它实际上包含 Environment、JNDI、Servlet API、Spring Web Reactive 这四类 mock objects。所谓的 mock objects 实际上是对一些 Spring 传统 API 接口的 mock implementation。
Unit Testing Support Classes
General Testing Utilities
- AopTestUtils
- ReflectionTestUtils
- TestSocketUtils
Spring MVC Testing Utilities
- org.springframework.test.web
- ModelAndViewAssert
Integration Test
如果需要 deploy a server、connectimg to other enterprise infrastructure、init a context。
集成测试的支持包括:
- To manage Spring IoC container caching between tests.
- To provide Dependency Injection of test fixture instances. 对固定装置对象的注入对于测试的帮助很大
- To provide transaction management appropriate to integration testing.
- To supply Spring-specific base classes that assist developers in writing integration tests.