单元测试框架之Junit
程序员文章站
2024-03-15 18:46:12
...
- 关于Junit,前面也有一篇文章做了简要概述,这次接着前面没讲到的地方展开
- 点击:测试方法之JUnit单元测试
一、Junit简介
1. 什么是Junit
- JUnit是一个 Java 编程语言的单元测试框架。
- JUnit在测试驱动的开发方面有很重要的发展
- JUnit是起源于 JUnit 的一个统称为 xUnit 的单元测试框架之一。
- JUnit 促进了“先测试后编码”的理念
- 强调建立测试数据的一段代码,可以先测试,然后再应用。
- 这个方法就好比“测试一点,编码一点,测试一点,编码一点……”,
- 增加了程序员的产量和程序的稳定性,可以减少程序员的压力和花费在排错上的时间
2. JUnit的特点
- JUnit 是一个开放的资源框架,用于编写和运行测试。
提供注释来识别测试方法。
提供断言来测试预期结果。
提供测试运行来运行测试。 - JUnit 测试可以自动运行并且检查自身结果并提供即时反馈。所以也没有必要人工梳理测试结果的报告。
- JUnit 测试可以被组织为测试套件,包含测试用例,甚至其他的测试套件。
- JUnit 在一个条中显示进度。如果运行良好则是绿色;如果运行失败,则变成红色。
3. 怎么用JUnit完成一个单元测试用例
- 单元测试用例是一部分代码,可以确保另一端代码(方法)按预期工作。
- 为了迅速达到预期的结果,就需要测试框架。
- JUnit 是 java 编程语言理想的单元测试框架。
- 一个正式的编写好的单元测试用例的特点是:已知输入和预期输出,即在测试执行前就已知。
- 已知输入需要测试的先决条件,预期输出需要测试后置条件。
- 每一项需求至少需要两个单元测试用例:一个正检验,一个负检验。
- 如果一个需求有子需求,每一个子需求必须至少有正检验和负检验两个测试用例。
二、Junit特性
1. JUnit-注释
- JUnit 中的注释为我们提供了测试方法的相关信息,哪些方法将会在测试方法前后应用,哪些方法将会在所有方法前后应用,哪些方法将会在执行中被忽略。
- JUnit 中的注释的列表以及他们的含义:
- @Before:初始化方法 对于每一个测试方法都要执行一次(注意与BeforeClass区别,后者是对于所有方法执行一次)
- @After:释放资源 对于每一个测试方法都要执行一次(注意与AfterClass区别,后者是对于所有方法执行一次)
- @Test:测试方法,在这里可以测试期望异常和超时时间
- @Test(expected=ArithmeticException.class)检查被测方法是否抛出相应异常
- @Ignore:忽略的测试方法
- @BeforeClass:针对所有测试,只执行一次,且必须为static void
- @AfterClass:针对所有测试,只执行一次,且必须为static void
2. 单元测试用例执行顺序
- @BeforeClass -> @Before -> @Test -> @After -> @AfterClass;
- 每一个测试方法的调用顺序为:
@Before -> @Test -> @After;
3. JUnit作为测试工具
- 它是是一整套固定的工具用于基线测试。
- 测试工具的目的是为了确保测试能够在共享且固定的
- 环境中运行,因此保证测试结果的可重复性。
- 包括:
1)在所有测试调用指令发起前的 setUp() 方法。
2)在测试方法运行后的 tearDown() 方法。
4. JUnit作为测试套件
- 也就是捆绑几个测试案例并且同时运行。
- 在 JUnit 中,@RunWith 和 @Suite 都被用作运行测试套件。
- TestSuite.java
package cn.yang.test;
import junit.framework.TestResult;
import junit.framework.TestSuite;
public class TestSuite1 {
public static void main(String[] args) {
//TestSuite是测试集合类
TestSuite su = new TestSuite(A01TestJunit1.class,A02TestJunit2.class);
//定义接收测试用例返回的结果集对象
TestResult res = new TestResult();
//运行测试用例,并获取结果
su.run(res);
//通过res对象,可以获取运行测试的数量
System.out.println("运行数量:" + res.runCount());
}
}
- A01TestJuni1.java
package cn.yang.test;
import junit.framework.TestCase;
import org.junit.Ignore;
import org.junit.Test;
public class A01TestJunit1 extends TestCase {
// @Ignore("123")
@Test
public void testAdd(){
int a = 5;
String tmp = null;
String str = "hello";
//检查是否相等
assertEquals("hello",str);
//检查是否为假
assertFalse(a > 6);
//检查对象是否为空
assertNull(tmp);
// int b = 12 / 0;
System.out.println("123");
}
}
- A02TestJunit2.java
package cn.yang.test;
import junit.framework.TestCase;
import org.junit.Before;
import org.junit.Test;
public class A02TestJunit2 extends TestCase{
private double d1;
private double d2;
@Before
public void setUp(){
d1 = 2.0;
d2 = 3.0;
System.out.println("开始");
}
@Test
public void testAdd(){
//被run的执行案例的次数
System.out.println("test case=" + this.countTestCases());
//获取测试用例的名字,默认和函数名一样
String name = this.getName();
System.out.println("测试用例的名字:" + name);
//设置TestName的名字
// this.setName("testNewAdd2");
String name2 = this.getName();
System.out.println("新的测试用例名字:" + name2);
double d4 = d1 + d2;
assertTrue(d4 == 5.0);
}
}
5. JUnit作为测试运行器
- 用于执行测试案例
- TestRunner
package cn.yang.test;
import org.junit.runner.JUnitCore;
import org.junit.runner.Result;
import org.junit.runner.notification.Failure;
public class TestRunner {
public static void main(String[] args) {
//JUnitCore它是运行所有测试类的核心入口类
//JUnitCore.runClasses(TestJunit.class);表示运行TestJunit.class这个测试类
//result执行的结果集,Junit作为测试运行器
Result result = JUnitCore.runClasses(TestJunit.class);
for (Failure failure : result.getFailures()) {// 对于执行失败的情况打印失败信息
System.out.println(failure.toString());
}
System.out.println(result.wasSuccessful());//执行成功的打印
}
}
6. JUnit作为测试分类
在编写和测试 JUnit 的重要分类。几种重要的分类如下:
- 包含一套断言方法的测试断言
- 包含规定运行多重测试工具的测试用例
- 包含收集执行测试用例结果的方法的测试结果
三、Junit的简单案例
1. 步骤
- 创建java工程
- 创建一个包,包中创建一个用来测试的类(MessageUtil.java)
- 创建一个测试类(TestJunit.java )
- 创建一个 TestRunner 类来运行测试用例(TestRunner.java)
- 正向,反向来一下
- JUnit的一些注意事项:
- 测试方法必须使用 @Test 修饰
- 测试方法必须使用 public void 进行修饰,不能带参数
- 一般使用单元测试会新建一个 test 目录存放测试代码,在生产部署的时候只需要将 test 目录下代码删除即可
- 测试代码的包应该和被测试代码包结构保持一致
- 测试单元中的每个方法必须可以独立测试,方法间不能有任何依赖
- 测试类一般使用 Test 作为类名的后缀
- 测试方法使一般用 test 作为方法名的前缀
2. JUnit核心模块说明
- 核心模块:
- Assert:方法的集合
- TestCase:一个定义了运行多重测试的固定装置
- TestResult:集合了执行测试样例的所有结果
- TestSuite:测试的集合
3. Assert断言
- void assertEquals(boolean expected,boolean actual) 检查两个变量或者等式是否平衡
- void assertFalse(boolean condition)检查条件是假的
- void assertNotNull(Object object) 检查对象不是空的
- void assertNull(Object object) 检查对象是空的
- void assertTrue(boolean condition) 检查条件为真
- void fail() 在没有报告的情况下使测试不通过
- 案例:
package ceShiBaoGao;
import io.appium.java_client.AppiumDriver;
import junit.framework.TestCase;
import org.openqa.selenium.By;
import org.openqa.selenium.remote.DesiredCapabilities;
import java.net.MalformedURLException;
import java.net.URL;
public class A04XiaoMiShop extends TestCase {
public static AppiumDriver dr;
public static boolean byElementIsExist(By by) {
try {
dr.findElement(by);
return true;
} catch (Exception e) {
return false;
}
}
public void testXiaoMi() throws MalformedURLException, InterruptedException {
// 获取设备和app的信息
DesiredCapabilities des = new DesiredCapabilities();
des.setCapability("platformName","Android");//平台
des.setCapability("deviceName","Android Emulator");//设备名
des.setCapability("platformVersion","5.1.1");//版本号
des.setCapability("noReset","true");//不重复安装
des.setCapability("appPackage","com.xiaomi.shop");//包名
//活动页
des.setCapability("appActivity","com.xiaomi.shop2.activity.MainActivity");
//创建连接appium对象
dr = new AppiumDriver(new URL("http://127.0.0.1:4723/wd/hub"),des);
Thread.sleep(3000);
//分类
dr.findElementByAndroidUIAutomator("text(\"分类\")").click();
Thread.sleep(3000);
//电视
dr.findElementByAndroidUIAutomator("text(\"电视\")").click();
Thread.sleep(3000);
//激光投影电视
dr.findElementByAndroidUIAutomator("text(\"激光投影电视\")").click();
Thread.sleep(3000);
//米家投影仪 青春版2
dr.findElementByAndroidUIAutomator("text(\"米家投影仪 青春版2\")").click();
boolean tt=byElementIsExist(By.id("com.xiaomi.shop2.plugin.goodsdetail:id/product_name"));
// 检查条件为真
assertTrue(tt);
Thread.sleep(3000);
dr.quit();
}
}
4. TestCase类
- int countTestCases() 为被run(TestResult result)执行的测试案例计数
- TestResult createResult() 创建一个默认的TestResult对象
- String getName() 获取TestCase的名称
- TestResult run() 一个运行这个测试的方便的方法,收集由TestResult对象产生的结果
- void run(TestResult result) 在TestResult中运行测试案例并收集结果
- void setName(String name) 设置TestCase的名称
- void setUp() 创建固定装置,例如打开一个网络连接
- void tearDown() 拆除固定装置,例如,关闭一个网络连接
- String toString() 返回测试案例的一个字符串表示
- 案例
package ceShiBaoGao;
import io.appium.java_client.AppiumDriver;
import junit.framework.TestCase;
import org.openqa.selenium.By;
import org.openqa.selenium.remote.DesiredCapabilities;
import java.net.MalformedURLException;
import java.net.URL;
public class QQlogin extends TestCase {
public static AppiumDriver dr;
public static boolean byElementIsExist(By by) {
try {
dr.findElement(by);
return true;
} catch (Exception e) {
return false;
}
}
public void setUp()throws MalformedURLException, InterruptedException{
System.out.println("in测试用例前");
//配置设备和app信息
//获取设备和app的信息
DesiredCapabilities des=new DesiredCapabilities();
des.setCapability("platformName","android");//平台
des.setCapability("deviceName","Android Emulator");//设备名
des.setCapability("platformVersion","5.1.1");//版本号
des.setCapability("noReset","true");//不要重复安装
des.setCapability("appPackage","com.tencent.qqlite");//包名
//活动页
des.setCapability("appActivity","com.tencent.mobileqq.activity.LoginActivity");
//创建连接appium的对象
dr = new AppiumDriver(new URL("http://127.0.0.1:4723/wd/hub"),des);
Thread.sleep(5000);//5秒
}
public void testQQin()throws MalformedURLException, InterruptedException{
System.out.println("in测试用例开始执行");
//1.点击qq输入框 desc:请输入QQ号码或手机或邮箱
dr.findElementByAccessibilityId("请输入QQ号码或手机或邮箱").click();
//2.清除qq输入框的qq号码
dr.findElementByAccessibilityId("请输入QQ号码或手机或邮箱").clear();
//3.输入qq号码
dr.findElementByAccessibilityId("请输入QQ号码或手机或邮箱").sendKeys("1530247895");
//4.点击密码输入框 desc:请输入密码
dr.findElementByAccessibilityId("请输入密码").click();
//5.清除密码输入框
dr.findElementByAccessibilityId("请输入密码").clear();
//6.输入密码
dr.findElementByAccessibilityId("请输入密码").sendKeys("yangheng123456");
//7.点击登陆按键 desc:登录QQ
dr.findElementByAccessibilityId("登录QQ").click();
Thread.sleep(3000);
//断言 id:com.tencent.qqlite:id/0
boolean tt=byElementIsExist(By.id("com.tencent.qqlite:id/title"));
assertTrue(tt);
}
public void tearDown()throws MalformedURLException, InterruptedException{
System.out.println("in测试用例后");
dr.quit();
}
}
5. TestResult类
- void addError(Test test,Throwable t) 在错误列表加入一个错误
- void addFailure(Test test,AssertionFailedError t)在失败列表中加入一个失败
- void endTest(Test test) 显示测试被编译的这个结果
- int errorCount()获取被检测出错误的数量
- Enumeration errors() 返回错误的详细信息
- int failureCount() 获取被检测出的失败的数量
- void run(TestCase test)运行 TestCase
- int int runCount() 获得运行测试的数量
- void startTest(Test test) 声明一个测试即将开始
- void stop()标明测试必须停止
- 案例:
import org.junit.runner.JUnitCore;
import org.junit.runner.Result;
import org.junit.runner.notification.Failure;
public class TestRunner3 {
public static void main(String[] args) {
//JUnitCore它是运行所有测试类的核心入口类
//JUnitCore.runClasses(TestJunit.class);表示运行TestJunit.class这个测试类
//result执行的结果集
Result result = JUnitCore.runClasses(TestJunit3.class);
for (Failure failure : result.getFailures()) {// 对于执行失败的情况打印失败信息
System.out.println(failure.toString());
}
System.out.println(result.wasSuccessful());//执行成功的打印
}
}
6. TestSuite类
- void addTest(Test test) 在套中加入测试
- void addTestSuite(Class<?extends TestCase>testClass) 将已经给定的类中的测试加到套中
- int countTestCases() 对这个测试即将运行的测试案例进行计数。
- String getName() 返回套的名称
- void run(TestResult result) 在TestResult中运行测试并收集结果
- void setName(String name) 设置套的名称
- Test testAt(int index) 给定的目录中返回测试
- int testCount() 返回套中测试的数量
- static Test warning(String message) 返回会失败的测试并且记录警告信息
- 案例:
JunitTestSuite.java
import junit.framework.*;
public class JunitTestSuite {
public static void main(String[] a) {
//TestSuite是测试的集合,把测试用例添加进集合
TestSuite suite = new TestSuite(TestJunit1.class, TestJunit2.class, TestJunit3.class );
//集合了执行测试样例的所有结果
TestResult result = new TestResult();
//在TestResult中运行测试并收集结果
suite.run(result);
//获取运行测试的数量
System.out.println("Number of test cases = " + result.runCount());
}
}
四、Junit4特性
1. 超时测试(@timeout)
- 对于那些逻辑很复杂,循环嵌套比较深的程序,很有可能出现死循环,因此一定要采取一些预防措施。限时测试是一个很好的解决方案。
- 我们给这些测试函数设定一个执行时间,超过了这个时间,他们就会被系统强行终止,并且系统还会向你汇报该函数结束的原因(超时),这样你就可以发现这些Bug了。
- 要实现这一功能,只需要给@Test标注加一个参数即可。 @Test(timeout = 1000) Timeout参数表明了你要设定的时间,单位为毫秒,因此1000就代表1秒
//测试乘方运算的一个测试用例(运行时间不能超过100ms)
B01 b=new B01();
@Test(timeout=1000)//1000时,ok,但100时,chengfang这个函数运行不完,抛异常
public void testChengfang_01(){
System.out.println("我是testChengfang_01");
int actualValue = b.chengfang(4);
int expectValue = 16;
assertEquals(actualValue,expectValue);
}
2. 忽略测试(@ignore)
- 如果你在写程序前做了很好的规划,那么哪些方法是什么功能都应该实现定下来。因此,即使该方法尚未完成,他的具体功能也是确定的,这也就意味着你可以为他编写测试用例。
- 如果你已经把该方法的测试用例写完,但该方法尚未完成,那么测试的时候一定是“失败”。这种失败和真正的失败是有区别的
- JUnit提供了一种方法来区别他们,那就是在这种测试函数的前面加上@Ignore标注,这个标注的含义就是“某些方法尚未完成,暂不参与此次测试”。
- 测试结果就会提示你有几个测试被忽略,而不是失败。一旦你完成了相应函数,只需要把@Ignore标注删去,就可以进行正常的测试。
- @ignore: 该元数据标记的测试方法在测试中会被忽略。当测试的方法还没有实现、方法已经过时,或者在某种条件下才能测试该方法,那么使用该标签来标示这个方法。同时,你可以为该标签传递一个String的参数,比如:
@lgnore(“该方法还没有实现”),在执行时仅会报告该方法没有实现,而不会运行测试方法。 - 案例:
//根据对详细设计文档的理解,这个预期是可以得出的
B01 b=new B01();
@Ignore("该方法尚未实现,在实现代码之后执行")
@Test
public void testmyfun(){
System.out.println("我是myfun");
int actualValue = b.myfun(4,2);
int expectValue = 6;
assertEquals(actualValue,expectValue);
}
3. 异常处理测试( @Test(expected=*.class))
- JAVA中的异常处理也是一个重点,因此你经常会编写一些需要抛出异常的函数。
- 如果你觉得一个函数应该抛出异常,但是它没抛出,这当然是Bug,并JUnit也考虑到了这一点,来帮助我们找到这种Bug。
- 例如,我们写的Calculator类有除法功能,如果除数是一个0,那么必然要抛出“除0异常”。因此,我们很有必要对这些进行测试。
- 代码如下: @Test(expected = ArithmeticException.class) 我们需要使用@Test标注的expected属性,将我们要检验的异常传递给他,这样JUnit框架就能自动帮我们检测是否抛出了我们指定的异常。
- 案例:
B03 b=new B03();
@Test(expected=ArithmeticException.class)//这个也抛异常,用例通过
//@Test //这个会抛异常,用例通过不了
public void testChu(){
System.out.println("我是testChu");
int actualValue = b.chu(5,0);
int expectValue = 5;
assertEquals(actualValue,expectValue);
}
4. 参数化运行器Runner
- 问题描述: 一个对考试分数进行评价的函数,返回值分别为“优秀,良好,一般,及格,不及格”,因此你在编写测试的时候,至少要写5个测试,把这5中情况都包含了,这确实是一件很麻烦的事情。在此情况下,参数化就有了用武之地
public class MyScore {
public String getLevel(int score){
String level;
if(score>90&& score<=100){
level = "优秀";
}else if(score>80 && score <=90){
level = "良好";
}else if(score>70 && score <=80){
level = "一般";
}else if(score>=60 && score <=70){
level = "及格";
}else if(score>=0 && score <60){
level = "不及格";
}else{
level = "输入数据异常!";
}
return level;
}
}
- 参数化Runner
- 步骤:
步骤1:这种测试专门生成一个新的类,而不能与其他测试共用同一个类
步骤2:定义一个待测试的类,使用@RunWith(Parameterized.class)修饰个类
步骤3:并且定义两个变量,一个用于存放参数,一个用于存放期待的结果
步骤4:定义测试数据的集合方法,必须使用@Parameters标注进行修饰,方法名可以自己定义,集合数据是一个二维数组,分别存储定义的两个成员变量
步骤5:定义构造方法(带两个参数),分别为成员变量初始化,注意与测试数据对齐
步骤6:编写单元测试用例、执行并查看结果
案例:
@RunWith(Parameterized.class)
public class ScoreParam {
//再创建两个变量,分别表示成绩score、预期结果expect
public int score;
public String expect;
//被测对象的变量要被声明为什么类型
public static MyScore ms;
@BeforeClass
public static void beforeClass(){
ms = new MyScore();
}
//通过方法,定义一个数据结构(数组),存放参数用的数据(一个是预期结果,一个是参数数据)
@Parameters
public static Collection<Object[]> data(){
return Arrays.asList(new Object[][]{
{"优秀",97},
{"良好",87},
{"良好",85},
{"一般",77},
{"及格",67},
{"不及格",57},
{"输入数据异常!",-10},
{"输入数据异常!",110},
});
}
//构造方法,构造方法的参数的顺序必须和data()中的参数列表保持一致
public ScoreParam(String expect,int score){
this.expect = expect;
this.score = score;
}
@Test
public void testScore(){
String actualValue = ms.getLevel(score);
assertEquals(actualValue,expect);
}
}
5. 打包测试(测试集)
- 创建一个普通的类文件
- 用@RunWith(Suite.class)修饰这个普通类,这个类就不再是普通类了,就是一个测试集合类
- 打包要测试的用例:@Suite.SuiteClasses({,})
- 直接执行该文件
- 案例:
package maker2;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
@RunWith(Suite.class)
@Suite.SuiteClasses({
Test1.class,
Test2.class,
Test3.class,
Test4.class,
Test5.class,
Test6.class
})
public class A02打包测试 {
}
6. 输出测试报告
6.1 eclipse输出测试报告
- Step01:所有的测试类写好后,对工程右键->export->General->Ant Buildfiles->next->选中工程名,默认的选项:name for and buildfile:build.xml;JUnit output directory:JUnit(不用手动创建,执行build.xml时会自动生成)->finish; 完成之后会在工程下会生成一个build.xml的蚂蚁图标的文件。
- Step02:右击build.xml文件,->run as->2 ant build…(不一定是2,可能是3或者4)。在Targets栏下选择要执行的targets. build[default]是默认的。选择待执行的测试类,注意要选择上junitreport,点击“Run”
- Step03:运行完成后,在工程下会生成一个junit的文件夹(刷新工程),下面有一系列的文件,其中index.html文件->open with->web browser,可以看到所有测试类的执行结果。
- Step04:查看单元测试运行结果。
6.2 idea输出测试报告
- Step01:所有的测试类写好后,点击如下位置的图标
- Step02:修改文件名,点击“OK”
- Step03:查看单元测试运行结果。
7. 判断app元素是否存在
public static boolean byElementIsExist(By by) {
try {
dr.findElement(by);
return true;
} catch (Exception e) {
return false;
}
}
//调用
boolean tt=byElementIsExist(By.id("com.xiaomi.shop.plugin.homepage:id/main_bottom_tab_category_txt"));
System.out.println("tt="+tt);
上一篇: 文科生的SQL初体验之查询
下一篇: Lambda表达式与函数式接口的关系