wwwh 法则 what,why,when, how
what
说在前面
测试分类:
(技术类型):单元测试->接口测试->UI测试,这可能是比较常见的测试金字塔( unit->api->ui )
(系统分层或测试阶段):单元测试->组件测试->集成测试->系统测试
(是否测试代码):黑盒,白盒,灰盒
(是否运行):静态测试,动态测试
单元测试:是对软件中的最小单元进行测试和验证,通俗来讲就是代码中的一个函数或一个类,单元测试一定是白盒测试
单元测试通常由开发工程师完成一般会伴随开发代码一起递交至代码库。单元测试属于最严格的软件测试手段,是最接近代码底层实现的验证手段,可以在软件开发的早期以最小的成本保证局部代码的质量
Test Pyramid理论基本大意是,
单元测试是基础,是我们应该花绝大多数时间去写的部分,
而集成测试等应该是冰山上面能看见的那一小部分。
why:
- 软件质量最简单、最有效的保证;
是目标代码最清晰、最有效的文档;
可以优化目标代码的设计;
是代码重构的保障;
是回归测试和持续集成的基石。
普通的单元测试
进行单元测试时,我们只需关心三样东西:
1.设置测试数据
2.设定预期结果
3.验证结果
when 在项目中如何进行单元测试考量?
- 项目适合不适合进行单元测试
项目中哪些模块适合单元测试
选用什么样的单元测试框架
如何执行单元测试
如何将单元测试融入ci进行持续集成
基于上面的考虑,如何在项目中开展单元测试。
并不是所有的项目都适合进行单元测试,即使进行单元测试,也应该是一些基础底层模块或者核心模块进行单元测试
选择合适的单元测试框架,Java中的TestNG、JUnit,Python中的Unittest、Pytest,PHP中的PHPUnit
将单元测试集成到CI流程当中
how:
等价类划分法:case设计方法:
边界值法:针对不同分类的case再进行边界参数case设计
针对代码实现的逻辑,应当根据产品业务逻辑进行预期的输入输出设计,而不能根据代码进行相关设计,那就没什么用了
桩代码(stub)和mock
单元测试是测试软件的最小单元,它应该是与软件其他部分相分隔,例如与真实的数据库、网络环境等分隔开的,从而只测试我们关心的逻辑部分。那么对于有外部依赖的单元如何进行测试呢?这里提到两个概念:桩代码和mock
桩代码:用来代替真实代码的临时代码,对于依赖的其它部分直接使用固定代码或固定数据返回,属于完全模拟外部依赖
mock:这个就很常见了,它的作用也是替代真实的代码或者数据,与桩代码不同的是,mock还是可以进行相关的规则制定,还需要关心mock函数的调用和返回数据,例如mock的多次调用是否异常等等。mock用来模拟一些交互进行一些断言判断测试是否通过。
但是两者都是为了对被测试函数进行隔离和补齐。
单元测试场景实例:
假设我们有一段业务逻辑,需要对给定的请求做处理,在这种情况下,倘若要手工构造发起一个请求,那想必是很麻烦蛋疼。首先我们需要把代码编译部署到测试服务器上,然后构造并发起一个请求,等待服务器接收到请求后,交给我们的业务进行处理。如下:
// 业务代码
public boolean handleRequest(HttpServletRequest request) {
String module = request.getParameter("module");
if ("live".equals(module)) {
// handle module live request
return true;
} else if ("user".equals(module)) {
// handle module user request
return true;
}
return false;
}
为了测试这么一点点代码,就需要我们额外付出那么多的操作,对于追求效率的程序员来说,这种重复操作&等待简直就是慢性自杀。这里的代码还是相对简单的,要是请求的内容更加复杂,难道还要花上大把时间研究如何构造出这么一个Http请求吗?
其实,测试这段逻辑,我们想要做的事情其实很简单,给定一个特定的输入,验证其输出结果是否正确。也就是,验证的过程,应该尽可能的简单方便,把大部分的时间耗费在验证过程上绝对是有问题的。
如果我们使用单元测试,搭配Mockito,完全可以写出如下测试,在代码提交之前,先在本地的JVM上过一遍测试。
结合Mockito+单元测试
@Test
public void handleRequestTestLive() throws Exception {
HttpServletRequest request = mock(HttpServletRequest);
when(request.getParameter("module")).thenReturn("live");
boolean ret = handleRequest(request);
assertEquals(true, ret)
}
@Test
public void handleRequestTestUser() throws Exception {
HttpServletRequest request = mock(HttpServletRequest);
when(request.getParameter("module")).thenReturn("user");
boolean ret = handleRequest(request);
assertEquals(true, ret)
}
@Test
public void handleRequestTestNone() throws Exception {
HttpServletRequest request = mock(HttpServletRequest);
when(request.getParameter("module")).thenReturn(null);
boolean ret = handleRequest(request);
assertEquals(false, ret)
}
首先,我们模拟出一个假对象,并设定这个假对象的行为,这个假对象的行为会影响我们业务逻辑的结果,所以我们可以在不同的测试用例里,设定假对象返回不同的行为,这样我们就能验证各种输入下,我们的业务逻辑是不是能够按我们的设想正常工作
有关于:Mock
Mock一词指效仿、模仿,在单元测试里,使用mock来构造一个“替身”。这个替身主要用于作为被测类的依赖关系的替代。
依赖关系 – 依赖关系是指在应用程序中一个类基于另一个类来执行其预定的功能.依赖关系通常都存在于所依赖的类的实例变量中.
被测类 – 在编写单元测试的时候, “单元”一词通常代表一个单独的类及为其编写的测试代码. 被测类指的就是其中被测试的类.
为什么需要mock呢?
真实对象具有不可确定的行为,产生不可预测的效果,(如:股票行情,天气预报
真实对象很难被创建的
真实对象的某些行为很难被触发
真实对象实际上还不存在的(和其他开发小组或者和新的硬件打交道)等等
在这些情形下,使用Mock能大大简化我们的测试难度。举个例子:
假定我们有如上的关系图:
类A依赖于类B和类C
类B又依赖于类D和类E
为了测试A,我们需要整个依赖