单元测试框架之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 的重要分类。几种重要的分类如下:

  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:所有的测试类写好后,点击如下位置的图标
    在这里插入图片描述
  • 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);
  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值