软件测试主要分成5类,范围从小到大分别为:单元测试->集成测试->功能测试->压力测试->验收测试。此系列中我们只讨论单元测试。
>> 单元测试的3种类型:
* 逻辑单元测试:主要针对一个单独的方法来检查代码,可以通过mock object或者stub来控制某个特定的测试方法的边界。
* 集成单元测试:主要用来测试在真实环境或者真实环境的一部分中不同组件之间的相互作用。例如测试一段访问某个数据库的代码是否真正有效的调用了数据库
* 功能单元测试:已经超出了集成单元测试的边界,目的是为了确认一个刺激响应。例如如果一个网页只能登录才能看到,否则重定向到登录页面。这个时候可以变形一个功能单元测试,通过向这个网页发送一个HTTP请求,然后验证返回是否是一个重定向HTTP状态码302等。
注:严格来讲,功能单元测试并不是纯粹的单元测试,也不是纯粹的功能测试。它相对于纯粹的单元测试而言,更多的依赖外部环境,但它又不像纯粹的功能测试那样会测试一个完整的工作流。我们之所以把功能单元测试放入我们讨论范围,是因为它通常在开发环境中作为一系列测试中一部分是非常有用的。
>> Junit的几个核心对象:
* Assert:让你定义想测试的条件,条件成立时,assert方法保持沉默,但是不成立的时候就会抛出异常
* 测试:一个以@Test注解的方法定义了一个测试。为了运行这个方法,JUnit会创建一个包含该测试方法的测试类的新实例,然后再调用这个被注解的方法,也就是说一个测试方法就会new一个测试类。
* 测试类:一个测试类是@Test方法的容器
* Suite:Suite允许你将测试类归组
* Runner:Runner类用来运行测试,JUnit4向后兼容,可以运行JUnit3的测试
>> 运行参数化测试:
来看一个Runner实例,一个测试方法通过传多个参数去让它执行多次,Parameterized是JUnit多个测试运行器Runner中的一个而已:
@RunWith(Parameterized.class) public class CalculatorParameterizedTest { private double expected; private double valueOne; private double valueTwo; public CalculatorParameterizedTest( double expected, double valueOne, double valueTwo) { this.expected = expected; this.valueOne = valueOne; this.valueTwo = valueTwo; } @Parameters public static Collection<Object[]> getTestParameters() { List<Object[]> params = Arrays.asList(new Object[][]{ {2,1,1}, {3,2,1}, {4d,2d,2d} }); System.out.println(params.size()); return params; } @Test public void testSum() { Calculator calculator = new Calculator(); assertThat(calculator.add(valueOne, valueTwo), is(expected)); } }
>> Junit最佳实践:
* 一次只能单元测试一个对象。当一个对象与其他复杂对象交互时候,可以使用可预测的测试对象将被测试对象包围起来。
* 一个单元测试等于一个@Test方法,不要试图把多个测试塞进一个方法中,这样导致的结果就是测试方法变得更加复杂,难以阅读也难以理解。
* 让测试改善你的代码,应当根据测试时候发现的问题重构代码,使其更加易于使用。极限编程提倡不要过早的添加功能。
>> 测试异常:
使用expected的注解参数测试异常示例
@Test(expected = RuntimeException.class) public void testAddRequestDuplicateName() { SampleRequest request = new SampleRequest(); SampleHandler handler = new SampleHandler(); controller.addHandler(request, handler); }
>> 超时测试:
使用timeout注解参数测试超时示例,使用@Ignore注解跳过该测试
@Test(timeout = 130) @Ignore(value = "Skip for now") public void testProcessMultipleRequestsTimeout() { Request request; Response response = new SampleResponse(); RequestHandler handler = new SampleHandler(); for (int i = 0; i < 99999; i++) { ... } }
>> 引入Hamcrest匹配器
有时候junit的assert语句太复杂很难看懂,这个时候可以使用hamcrest框架的短语来简化assert语句。Hamcrest是一个库,包含了大量的匹配器对象(也称约束或者谓语),它可以被植入到其他几种开发语言比如java、C++、Objective-C、Python和PHP,请注意,Hamcrest本身不是一个测试框架,确切的说,它可以帮你通过声明方式指定简单的匹配规则,尤其适用于单元测试,不过也可以应用到其他领域。
@Test public void testWithHamcrest() { assertThat(values, hasItem(anyOf(equalTo("one"), equalTo("two"), equalTo("three")))); }
一些常用的Hamcrest匹配器:
anything | 绝对匹配,更加可读性 |
is | 仅用于改善可读性 |
allOf | 检查是否与所有包含的匹配器匹配(相对于&&) |
anyOf | 检查是否与任意包含的匹配器匹配(相对于||) |
not | 非(相对于!) |
instanceOf、isCompatibleType | 对象类型 |
sameInstance | 测试对象引用是否指向同一个对象 |
notNullValue、nullValue | null值或非null值 |
hasProperty | 测试JavaBean是否具有某属性 |
hasEntry、hasKey、hasValue | 测试Map |
hasItem、hasItems | 测试集合 |
closeTo、greaterThan、greaterThanOrEqual、 lessThan、lessThanOrEqual |
测试数字的大小 |
equalToIgnoringCase | 测试字符串相同与否,忽略大小写 |
equalToIgnoringWhiteSpace | 测试字符串相同与否,忽略空白 |
containsString、endsWith、startWith | 测试字符串包含、结尾、开头 |