单元测试在计算机编程中单元测试(Unit Testing)又称为模块测试,是针对代码模块(软件设计的最小单位)来进行正确性检验的测试工作。在过程化编程可能是整个模块、但更多是函数或过程等;对于面向对象编程通常是整个接口,比如类,也可能是方法。
单元测试基于测试用例进行组织。理想的情况下,测试用例互相独立,为此使用桩、mock、fake、服务虚拟化和测试框架等,通常由开发或者白盒测试人员书写。对于数据库和缓存等操作,传统建议用mock的方式进行单元测试。但是依赖较多的情况下mock比较耗时耗力,为此尽量采用本地构建实际数据、缓存等中间件的方式,尽量少使用mock,对于网络依赖,则尽量采用服务虚拟化。
单元测试的目的:尽早发现bug(提前准备单元测试更好地考虑了输入、输出和错误);快速变化(回归单元测试利于重构);简化集成(自底向上);文档;TDD;增强自信等。在比较理想的情况下,单元测试能发现占比40%左右的BUG。
单元测试主要针对各种输入组合进行测试,常用的方法有等价类划分和边界值等。同时特别关注控制流和数据流,通过关注if、for、switch、while等分支条件,尽量提高覆盖率。另外单元测试要格外关注异常处理逻辑。
单元测试重视覆盖率,但是不为了追求覆盖率而不顾实际情况。通常一般的公司在覆盖率50%左右能达到比较好的性价比。追求90%以上的覆盖率通常比较有难度,即便是google之类的领导性的公司,代码覆盖率也只达到了80%多。
单元测试能提前发现一些集成、系统测试阶段的问题,但是不能替代后续测试。
单元测试在代码高内聚松耦合及公共库提取完整等情况比较容易进行测试。
单元测试通常与小提交、每次提交触发测试等持续集成配合使用。
单元测试自动生成用例是较前沿的技术,相关商业工具有:AgitarOne、Jtest 、SilverMark Test Mentor
开源工具有:CodePlex AnalytiX、EvoSuite、Randoop、Palus、Daikon, Eclat, Javalanche, Java PathFinder, JCrasher, jWalk, Korat, RecGen和ReCrash等。当然有不少公司直接用python控制各种语言生成单元测试用例。
参考资料:https://en.wikipedia.org/wiki/Unit_testing
不写单元测试的借口:
* 编写单元测试太花时间了
* 运行测试的时间太长了
* 测试代码并不是我的工作
* 代码太复杂了,依赖太多,不能做单元测试
* 没有单元测试,这些问题照样能发现。
哪些项目最适合单元测试?
* 平台类库性质的项目
* 规模大的,需要长时间维护的
* 正确性要求极高的项目
哪些代码需要测试?
* UI层
* UI Business
* 业务层
* 数据层
一般只需要测公开方法
单元测试的前提
* 程序层次结构清楚
* 层间解耦
* 外部依赖小
最最糟糕的被测方法
* 业务代码混在UI层方法中
* 数据库操作混在业务代码中
* 外部依赖很多
UI层的测试
* 不需要具体的业务实现
* 可以看到在不同业务下的UI表现
* 关键字: 静态Mock
业务层的测试
* 不需要具体的数据层
* 不需要UI层来验证
* 关键字: 动态Mock
业务层的测试,为了去除依赖,mock是必须的。
但在使用每一个Mock前,请想想,是否可以通过重构,摆脱这个依赖。
到底是先有代码,后有测试;还是先有测试,再有代码?这是个问题!
据我的经验,一上来啥代码都没有,就写测试,估计就是两眼一抹黑,对着屏幕发愣。。。
个人建议:先定好接口,然后对于具体实现,就可以使用TDD
自动化
关键字:人工干预(比如需要网络连接、数据库等),通过Mock,去除干预。
关键字:DailyBuild 。
好的测试:可重复、独立的
开源的以xunit为主。商业工具有Typemock Isolator.NET/Isolator++, TBrun, JustMock, Parasoft Development Testing (Jtest, Parasoft C/C++test, dotTEST), Testwell CTA++ and VectorCAST/C++等。
Python: unittest、pytest(推荐)、nose
Java: Junit(推荐)、TestNG、Spock
在软件工程中,服务虚拟化是在基于异构组件(如API驱动、基于云计算的应用及SOA等)的应用中仿真具体应用行为的方法。
使用场景:
尚未完成
仍在发展
第三方或合伙人控制
测试的时间和容量有限制
很难规定或在测试环境中配置
需要由不同的团队具有不同的测试数据设置和其他要求的同时访问
负载和性能测试的成本很高
常见虚拟资产的创建的方法:
1,录制真实通信
2,日志
3,分析接口规格
4,自定义行为控制接口
在SOA等体系中,使用mock工作量很大,敏捷开发加快了迭代速度,为此mock较多的情况下,通常不如采用服务虚拟化。
简单的虚拟化通常通过配置hosts文件,把目标服务器指向自定义服务器。python的flask框架因为开发速度快,经常被采用。在TCP和UDP层,python socket经常使用。这块不仅是对单元,对接口甚至是系统等测试都意义重大。
常用的框架如下:
https://github.com/oarrabi/mockpy(貌似只支持MAC平台),以HTTP模拟为主。
阿里巴巴出品:https://github.com/thx/RAP 主要为帮助web工程师管理API。RAP通过GUI工具帮助WEB工程师更高效的管理接口文档,同时通过分析接口结构自动生成Mock数据、校验真实接口的正确性,使接口文档成为开发流程中的强依赖。有了结构化的API数据,RAP可以做的更多,而我们可以避免更多重复劳动。
wiremock: http://wiremock.org/ https://github.com/tomakehurst/wiremock (推荐)
HTTP响应打桩,可匹配URL,标题和正文内容
请求验证
在单元测试运行,作为一个独立的进程或为WAR应用
Java API,JSON文件和JSON加HTTP配置
桩录制/回放
故障注入
每个请求条件代理
浏览器代理支持请求检查和更换
状态的行为模拟
可配置的响应延迟
junitperf用于单元性能测试。
Feed4junit用于提供数据驱动。
https://github.com/jamesdbloom/mockserver 在Java, JavaScript and Ruby中mockHTTP或HTTPS。MockServer内置代理内省流量并支持端口转发、web转发等。
https://github.com/splunk/eventgen 是Splunk的事件生成器,基于Python。
https://github.com/dreamhead/moco (比较推荐,支持HTTP和socket)
@FunctionalInterface public interface Scoreable { int getScore(); }
import java.util.*; public class ScoreCollection { private List<Scoreable> scores = new ArrayList<>(); public void add(Scoreable scoreable) { scores.add(scoreable); } public int arithmeticMean() { int total = scores.stream().mapToInt(Scoreable::getScore).sum(); return total / scores.size(); } }
import static org.junit.Assert.*; import static org.hamcrest.CoreMatchers.*; import org.junit.*; public class ScoreCollectionTest { @Test public void answersArithmeticMeanOfTwoNumbers() { // Arrange ScoreCollection collection = new ScoreCollection(); collection.add(() -> 5); collection.add(() -> 7); // Act int actualResult = collection.arithmeticMean(); // Assert assertThat(actualResult, equalTo(6)); } }
参考资料:https://en.wikipedia.org/wiki/Service_virtualization
标准断言:
JUnit的Assert类提供一些静态方法,一般以assert开头,参数分别为错误信息,期望值,实际值。如果期望与实际不符,将抛出AssertionException异常。
以下给出这类方法的概览,[]中的参数为可选,类型为String。
语句 | 描述 |
assertTrue([message,] boolean condition) | 确认布尔条件为真。 |
assertFalse([message,] boolean condition) | 确认布尔条件为假 |
assertEquals([message,] expected, actual) | 确认返回等于期望,数组等比较的是地址而非内容。 |
assertEquals([message,] expected, actual, tolerance) | 测试float或double相等。 tolerance为容许的误差。 |
assertNull([message,] object) | 对象为null |
assertNotNull([message,] object) | 对象非null |
assertSame([message,] expected, actual) | 变量指向同一对象. |
assertNotSame([message,] expected, actual) | 变量指向不同对象 |
assertEquals(double expected,double actual, double delta)可以解决double的精度误差,对于钱,建议使用BigDecimal类型。另外还有个特殊的fail(message)。
Hamcrest对断言进行了扩展
assertThat扩展了断言,语法如下:
public static void assertThat(Object actual, Matcher matcher)。
导入import static org.hamcrest.CoreMatchers.*;
Matcher实现了org.hamcrest.Matcher接口,并且可以组合,比如:
• assertThat(calculatedTax, is(not(thirtyPercent)) );
• assertThat(phdStudentList, hasItem(DrJohn) );
• assertThat(manchesterUnitedClub, both( is(EPL_Champion)).and(is(UEFA_Champions_League_Champion)) );
Matcher列表如下:
allOf , anyOf , both , either , describedAs , everyItem , is , isA , anything ,
hasItem , hasItems , equalTo , any , instanceOf , not , nullValue , notNullValue ,
sameInstance , theInstance , startsWith , endsWith , and containsString。
import static org.hamcrest.CoreMatchers.*; import static org.junit.Assert.assertThat; import java.util.Arrays; import java.util.List; import org.junit.Test; public class AssertThatTest { @Test public void test_matcher_behavior() throws Exception { int myAge = 30; //examine the exact match with equalTo and is assertThat(myAge, equalTo(30)); assertThat(myAge, is(30)); //examine partial match with not() assertThat(myAge, not(equalTo(33))); assertThat(myAge, is(not(33))); } @Test public void verify_multiple_values() throws Exception { double myMarks = 100.00; assertThat(myMarks, either(is(100.00)).or(is(90.9))); assertThat(myMarks, both(not(99.99)).and(not(60.00))); assertThat(myMarks, anyOf(is(100.00),is(1.00),is(55.00),is(88.00),is(67.8))); assertThat(myMarks, not(anyOf(is(0.00),is(200.00)))); assertThat(myMarks, not(allOf(is(1.00),is(100.00),is(30.00)))); } @Test public void verify_collection_values() throws Exception { List<Double> salary =Arrays.asList(50.0, 200.0, 500.0); assertThat(salary, hasItem(50.00)); assertThat(salary, hasItems(50.00, 200.00)); assertThat(salary, not(hasItem(1.00))); } @Test public void verify_Strings() throws Exception { String myName = "John Jr Dale"; assertThat(myName, startsWith("John")); assertThat(myName, endsWith("Dale")); assertThat(myName, containsString("Jr")); } }