欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

单元测试框架之Junit

程序员文章站 2024-03-15 18:46:12
...

一、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 的重要分类。几种重要的分类如下:

  1. 包含一套断言方法的测试断言
  2. 包含规定运行多重测试工具的测试用例
  3. 包含收集执行测试用例结果的方法的测试结果

三、Junit的简单案例

1. 步骤

  1. 创建java工程
  2. 创建一个包,包中创建一个用来测试的类(MessageUtil.java)
  3. 创建一个测试类(TestJunit.java )
  4. 创建一个 TestRunner 类来运行测试用例(TestRunner.java)
  5. 正向,反向来一下
  6. JUnit的一些注意事项:
    1. 测试方法必须使用 @Test 修饰
    2. 测试方法必须使用 public void 进行修饰,不能带参数
    3. 一般使用单元测试会新建一个 test 目录存放测试代码,在生产部署的时候只需要将 test 目录下代码删除即可
    4. 测试代码的包应该和被测试代码包结构保持一致
    5. 测试单元中的每个方法必须可以独立测试,方法间不能有任何依赖
    6. 测试类一般使用 Test 作为类名的后缀
    7. 测试方法使一般用 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:所有的测试类写好后,点击如下位置的图标
    单元测试框架之Junit
  • Step02:修改文件名,点击“OK”
    单元测试框架之Junit
  • 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);