前言
从昨天开始大部分同学都开始在家办公了,我也在14day home quarantine中,希望大家都健健康康,迎接春天的道来。
进入正题,《重构_改善既有代码的设计》一书中,曾提到,重构的第1步为建立一组可靠的测试环境,可见重构的目的不仅仅为了代码结构上的优化。
而且,在日常工作中,仅依赖于QA同学的测试用例,难免不完全,毕竟作为代码编写者的你,更加理解会有什么样的边界情况。
JUnit测试框架
日常工作中,也会用JUnit编写单元测试,但总觉得不太规整,而且有些测试用例略显盲目,不完全,故借书系统学习下。
- 四大特性:
1、测试工具 2、测试套件 3、测试运行器 4、测试分类
测试工具
- 测试工具的目的是为了确保测试能够在共享且固定的环境中运行。
1、在所有测试调用指令发起前的 setUp() 方法。 2、在测试方法运行后的 tearDown() 方法。
代码示例
public class JavaTest extends TestCase {
private FileReader fileReader = null;
/**
* 初始化运行环境、使用资源
*
* @date 11:56 2020-02-11
**/
@Override
protected void setUp() throws Exception {
String fileName = "/Users/jigangpark/Work/JavaProjects/jvmtest/src/main/java/junit/data.txt";
fileReader = new FileReader(fileName);
}
public void testPrint() throws Exception {
char[] a = new char[50];
// 读取数组中的内容
fileReader.read(a);
for (char c : a) {
// 一个一个打印字符
System.out.print(c);
}
}
/**
* 对于资源进行回收,关闭运行环境
*
* @date 12:01 2020-02-11
**/
@Override
protected void tearDown() throws Exception {
fileReader.close();
}
}
- 可以进入调试模式看下,当测试用例跑完,会走到
fileReader.close()
。
测试套件
- 捆绑几个测试案例并且同时运行,通常使用
@RunWith
和@Suite
注解来表示。
代码示例
/**
* TestSuite.java
* @date 2020-02-11 14:19
**/
@RunWith(Suite.class)
@Suite.SuiteClasses({
TestJunit1.class, TestJunit2.class
})
public class TestSuite {
}
/**
* TestJunit1.java
**/
public class TestJunit1 {
String message = "Robert";
MessageUtil messageUtil = new MessageUtil(message);
@Test
public void testPrintMessage() {
System.out.println("Inside testPrintMessage()");
Assert.assertEquals(message, messageUtil.printMessage());
}
}
/**
* TestJunit2.java
**/
public class TestJunit2 {
String message = "Robert";
MessageUtil messageUtil = new MessageUtil(message);
@Test
public void testSalutationMessage() {
System.out.println("Inside testSalutationMessage()");
message = "Hi!" + "Robert";
Assert.assertEquals(message, messageUtil.salutationMessage());
}
}
/**
* MessageUtil.java
**/
public class MessageUtil {
private final String message;
public MessageUtil(String message) {
this.message = message;
}
public String printMessage() {
return message;
}
public String salutationMessage() {
return "Hi!" + message;
}
}
- 运行
TestSuite
,会发现会运行TestJunit1
和TestJunit2
两个测试类,运行结果如下:
// Tests passed:2 of 2 tests
Inside testPrintMessage()
Inside testSalutationMessage()
Process finished with exit code 0
测试运行器
- 测试运行器用于执行测试用例,可以使用测试运行器
代码示例
public class TestRunner {
public static void main(String[] args) {
Result result = JUnitCore.runClasses(TestSuite.class);
for (Failure failure : result.getFailures()) {
System.out.println(failure.toString());
}
System.out.println(result.wasSuccessful());
}
}
- 运行结果如下,会运行测试套件
TestSuite
,同样会运行测试套件里的两个测试用例,这里我们故意使其中一个测试用例运行结果不正确。
Inside testPrintMessage()
Inside testSalutationMessage()
// 未通过的测试用例错误信息
testSalutationMessage(junit.TestJunit2): expected:<Hi[!]Robert> but was:<Hi[]Robert>
// 测试用例未全部通过
false
测试分类
测试分类是在编写和测试 JUnit 的重要分类。几种重要的分类如下:
1、包含一套断言方法的测试断言 2、包含规定运行多重测试工具的测试用例 3、包含收集执行测试用例结果的方法的测试结果
单元测试和功能测试
-
笔者通常的做法是,针对一个接口(interface)使用插件生成Junit测试类,这样每个方法都会有对应的测试方法(@Test),但是显得毫无章法。
-
其实上述情况都属于单元测试的范畴,而功能测试是用来保证软件能够正常运作,QA团队也是从客户的角度保障质量。一般而言,功能测试尽可能把整个系统当作一个黑盒,通过app、网页等GUI来操作整个系统,不会关心具体实现。
-
针对功能测试暴露出来的问题,笔者也通常只是改改代码,无问题了即可。但书中给出了一个给笔者启发的原则。
每当你收到bug报告,请先写一个单元测试来暴露bug。
- 笔者受到的启发:这样针对功能测试的bug,也能用Junit来表示,有时这些测试用例会比单纯插件生成的@Test方法有用。
结语
笔者从自己实际开发工作中,无法有效地组织单元测试结构的痛点出发,从《重构_改善既有代码的设计》中,得到了如下启发,笔者以后在实际开发中也会尽量采用这种思路来进行编码,希望对大家有些帮助。
1、使用一个测试套件来表示一个业务场景(可以是一个接口,一个功能涉及到的一些接口) 2、单元测试需要尽量包含边界情况(这些边界往往作为作者的你最清楚),QA团队会帮你捕捉大多数错误,但是一些细节错误还是要靠自己! 3、QA提出的功能bug要在测试套件中添加测试用例,可以认为使用Junit测试用例表示一个功能测试。 4、对功能进行修改后,运行一次测试套件,验证其准确性,往往会遇到改了代码,另一个地方出问题。
- 路漫漫其修远兮,吾将上下而求索
- 继续摸鱼去了!希望春天尽早来临!
参考文献
- 重构_改善既有代码的设计
- www.w3cschool.cn/junit/ari41…