一、导出 Junit 自动化测试脚本
1、对于单元测试框架来讲,主要完成以下三件事:
- 提供用例组织与执行:
当你的测试用例只有几条时,可以不必考虑用例的组织,但是,当测试用例达到成百上千条时,大量的测试用例堆砌在一起,就产生了扩展性与维护性等问题,需要考虑用例的规范与组织问题了。单元测试框架就是用来解决这个问题的。 - 提供丰富的比较方法:
不论是功能测试,还是单元测试,在用例执行完成之后都需要将实际结果与进行预期结果的进行比较(断言),从而断定用例是否执行通过。单元测试框架一般会提供丰富的断言方法:例如,判断相等/不等、包含/不包含、True/False 的断言方法等。 - 提供丰富的日志:
当测试用例执行失败时能抛出清晰的失败原因,当所有用例执行完成后能提供丰富的执行结果。例如,总执行时间、失败用例数、成功用例数等。
一般的单元测试框架都会提供这些功能,从单元测试框架的这些特性来看,它是同样适用于 Web 自动化用例的开发与执行。
2、在eclipse中进行Junit单元测试
- 直接在包名右键build path 引入Junit的jar包即可;
3、导出 Junit 自动化测试脚本
将 Junit 单元测试框架和 Selenium 自动化测试脚本,这两个技术组合起来做自动化测试:我们可以将 Selenium IDE 编写导出为所需要的格式,从而帮助我们理解自动化脚本的编写。
(1)首先,通过 Selenium IDE 录制一个测试用例,选择菜单栏“文件”–>“Export Test Case As…”弹出二级菜单,这里罗列Selenium IDE所支持的导出类型:编程语言/测试框架/WebDriver(或Remote Control)
Selenium IDE 提供多语言与测试框架的自动化脚本生成,对于学习不同编程语言下的自动化测试脚本开发提供很好的帮助和参考。
(2)因为我们当前使用的自动化测试开发语言为 Java,单元测试框架为 Junit4,自动测试框架为 WebDriver,所以选择“Java/Junit4/WebDriver”选项,将脚本保存到指定位置。
- **注意:**在导出的类型中相比它其语言多了一个选项,叫“Java/Junit4/ WebDriver Backed”选项,它是一种混合的写法,允许让你用 WebDriver 的方式去运行 Remote Control 的脚本。
下面为导出的 WebDriver 代码 - webdriver.java:
package com.mypro.unit;
import java.util.regex.Pattern;
import java.util.concurrent.TimeUnit;
import org.junit.*; //首先要引入 Junit 框架
import static org.junit.Assert.*;
import static org.hamcrest.CoreMatchers.*;
import org.openqa.selenium.*;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.support.ui.Select;
public class webdriver {
private WebDriver driver; //初始化定义私有变量 driver 为驱动
private String baseUrl; //初始化定义私有变量 baseUrl 为基本 URL 地址
private boolean acceptNextAlert = true; //初始化定义私有变量 acceptNextAlert 表示是否继续接受下一个警告
private StringBuffer verificationErrors = new StringBuffer(); //初始化定义私有变量 verificationErrors 用来存放错误的字符串变量
@Before
public void setUp() throws Exception {
driver = new ChromeDriver();
baseUrl = "https://www.baidu.com/";
driver.manage().timeouts().implicitlyWait(30, TimeUnit.SECONDS);
}
//setUp()用于设置初始化工作,在每一个测试用例前先被执行,它与 tearDown()方法相呼应,后者在每一个测试用例执行后被执行
//这里的初始化工作定义了浏览器驱动和基础 URL 地址,并且还设定的浏览器的超时时间为 30 秒
@Test
public void testWebdriver() throws Exception {
driver.get(baseUrl + "/");
driver.findElement(By.id("kw")).clear();
driver.findElement(By.id("kw")).sendKeys("selenium");
driver.findElement(By.id("su")).click();
}
//testWebdriver 中放置的就是我们前面一直练习的测试脚本
@After
public void tearDown() throws Exception {
driver.quit();
String verificationErrorString = verificationErrors.toString();
if (!"".equals(verificationErrorString)) {
fail(verificationErrorString);
}
}
//tearDown()方法在每个测试方法执行后调用,
//这个方法用于完成测试用例执行后的清理工作,如退出浏览器、关闭驱动,恢复用例执行前的状态等
//在@Before - setUp()方法中定义了 verificationErrors 为空字符串,这里通过 assertEqual()比较其是否为空,
//如果为空说明用例执行的过程过程中没有出现异常,否则不为空将抛出 AssertionError 异常。
private boolean isElementPresent(By by) {
try {
driver.findElement(by);
return true;
} catch (NoSuchElementException e) {
return false;
}
}
//isElementPresent()方法用来查找页面元素是否存在,通过 find_element()来接收元素的定位
//如果能找到元素返回 ture,否则出现异常并返回 flase
private boolean isAlertPresent() {
try {
driver.switchTo().alert();
return true;
} catch (NoAlertPresentException e) {
return false;
}
}
//isAlertPresent()方法用于判断当前页面是否存在警告框,利用 WebDriver 所提供的 switchTo().alert()方法来捕捉页面上警告框
//如果捕捉到警告框则返回 True,否则将抛出 NoAlertPresentException 类型异常,并返回 Flase
//不过,不管页面是否出现警告框,返回结果都为 True
//So可以调整该方法为switchTo().alert().getText() 用于获取当前页面上的警告提示信息
//可以获取到将会返回 True,获取不到则返回 False.
private String closeAlertAndGetItsText() {
try {
Alert alert = driver.switchTo().alert();
String alertText = alert.getText();
if (acceptNextAlert) {
alert.accept();
} else {
alert.dismiss();
}
return alertText;
} finally {
acceptNextAlert = true;
}
}
//closeAlertAndGetItsText()关闭警告并且获得警告信息
//首先通过 switchTo().alert()获得警告,通过.getText()获得警告框信息
//接着通过 if 语句判断 acceptNextAlert 的状态,在初始化私有变量的时候状态为 Ture
//如果为 Ture,通过 accept()接受警告。否则 dismiss()忽略此警告
}
4、那 Junit 怎么知道哪个方法是初始化方法,哪个是测试用例?
每个方法上面都有一个**@,这种用法叫“注解”**,是在 Java1.5 之后引入的新特性。
- @Before 注释的方法会在每个用例之前执行
- @Test 注释的方法表示一个测试用例
- @After 注释的方法会在每个用例后执行
二、Junit 单元测试框架解析
1、什么是单元测试
单元测试负责对最小的软件设计单元(模块)进行验证,它使用软件设计文档中对模块的描述作为指南,对重要的程序分支进行测试以发现模块中的错误。
在 Java 语言下有 Junit 和 TestNG两个单元测试框架,其中 Junit 单元测试框架诞生较早,是 xunit 系列的单元测试框架的始祖,奠定了单元测试框架的思想与模型。
- 注意:不用单元测试框架也能写单元测试,因为单元测试本身就是通过一段代码去验证另一个代码
2、不用测试框架的测试
首先创建一个被测试类 Count.java
package com.junit.test;
public class Count {
public int add(int x ,int y){
return x+y;
}
}
如果我们不用测试框架,我们会用写一个 main()方法对上面的代码进行验证。全部代码如下:
package com.junit.test;
import com.junit.test.count; //引入 Count 类
public class Test {
public static void main(String args[]){
int z = new count().add(3,5); //向 add 类传递两个参数 3 和 5
if (z==8){ //判断返回结果是否等于 8
System.out.println("pass!");
}else{
System.out.println("failed!");
}
}
}
不难发现这种手工测试方法存在许多问题:
- 需要花费很多的时间来写测试代码;
- 测试代码不可重用,一段测试代码对应一段被测代码,被测代码变,测试代码就没用了;
- 无法同时运行多个测试代码。假如有一百个被测试代码,要一个一个的运行测试代码,用肉眼观察结果,效率低还容易出错;
为了让单元测试代码能够被测试和维护人员更容易地理解,最好的解决办法是让开发人员遵循一定的规范来编写用于测试的代码,具体到 Java 程序员来讲,则是要采用 Junit 这一自动测试框架来构造单元测试用例。
3、通过 Junit 单元测试框架来写单元测试
package com.junit.test;
import com.junit.test.count;
import org.junit.Test;
import static org.junit.Assert.*;
public class test {
@Test
public void testAdd(){
int z = new count().add(3,5);
assertEquals(z,8);
}
}
上面的代码通过 junit 编写测试用例,这里用到了两个知识点
- 通过@Test 注解 testAdd()方法为一个测试方法,里面放置的就是我们之前一直练习的测试脚本;
- 通过 assertEquals()来比较两个数是否相等,这样的写法要比通过 if 语句判断简洁得多;
Junit 执行结果:
4、错误类型
查看上一节中的 Junit 的测试结果,Junit 会有两种错误类型:Errors 和 Failures;
那么什么情况下用例会执行 Errors,什么情况又会是执行 Failures?
(1)Errors:表示程序本身错误
public class test {
@Test
public void testAdd() {
int z=new count().add(5,3);
assertEquals(8,z);
int a=8/0; //这一句是有错误的
}
}
这个错误比较隐蔽,在 Java 中被除数不能为 0,所以 8/0 会有问题,一般会报异常:java.lang.ArithmeticException: / by zero , 但这个异常错误,在 Junit 的执行结果中不会抛出。
Junit 执行结果:
(2)Failures: 是指测试失败
public class test {
@Test
public void testAdd(){
int z=new count().add(3,5);
assertEquals(z,9);
}
}
这个测试用例就比较容易看出哪里有问题了,通过 assertEquals()方法比较的两个数结果不相等;
5、常用注解
- @Test:测试方法
- (A) (expected=XXEception.class)
- (B) (timeout=xxx)
- @Ignore: 被忽略的测试方法
- @Before: 每一个测试方法之前运行
- @After : 每一个测试方法之后运行
- @BefreClass 所有测试开始之前运行
- @AfterClass 所有测试结果之后运行
package com.junit.test;
import ......
public class tests {
@BeforeClass //的所有方法运行之前运行
public static void beforeClass(){
System.out.println("-------------------beforeClass");
}
@AfterClass //在所有方法运行之后运行
public static void afterClass(){
System.out.println("-------------------afterClass");
}
@Before //每个测试方法运行之前运行
public void before(){
System.out.println("=====before");
}
@After //每个测试方法运行之后运行
public void after(){
System.out.println("=====after");
}
@Test //测试用例
public void testAdd() {
int z=new count().add(5,3);
assertEquals(8,z);
System.out.println("test Run through");
}
@Test () //测试用例
public void testdivision(){
System.out.println("in Test Division");
}
@Ignore //表示这个方法是不被运行的
@Test
(expected=java.lang.ArithmeticException.class,timeout=100) //timeout 表示要求方法在 100 毫秒内运行完成,否则报错
public void testDivide(){
int z =new count().divide(8,2);
}
}
运行结果如下:
- 标记红星方法在每个方法开始和结尾都运行一次;
- 标记绿星的方法只在所有方法的开始和结尾运行一次;
6、断言方法
在执行用例的过程中,最终用例是否执行通过,是通过判断测试得到的实际结果与预期结果相等。
Junit框架提供下面这些方法用于测试结果的判断:
|方 法 |检 查|
|–|
|assertArrayEquals(expecteds, actuals) |查看两个数组是否相等|
|assertEquals(expected, actual) |查看两个对象是否相等,若值不相等测试失败。类似于字符串比较使用的 equals()方法|
|assertNotEquals(first, second) |查看两个对象是否不相等,若值相等测试失败|
|assertNull(object)| 查看对象是否为空|
|assertNotNull(object) |查看对象是否不为空|
|assertSame(expected, actual) |查看两个对象的引用是否相等。类似于使用“==”比较两个对象|
|assertNotSame(unexpected, actual)| 查看两个对象的引用是否不相等。类似于使用“!=”比较两个对象|
|assertTrue(condition) |查看运行结果是否为 true|
|assertFalse(condition) |查看运行结果是否为 false|
|assertThat(actual, matcher) |查看实际值是否满足指定的条件|
|fail() |让测试失败|
下面来实现一个判断是否为质数功能,所谓的质数(又叫素数)是指只能被 1 和它本身整除的数;
public class testMethod {
public static Boolean Prime(int n) { //判断一个数是否为素数
for (int i = 2; i < Math.sqrt(n); i++) {
if (n % i == 0)
return false;
}
return true;
}
@Test
public void test_case(){
int n = 7;
assertTrue(testMethod.Prime(n)); //用于测试表达式是 true
}
}
7、批量执行测试用例
如果你在一个类中实现了多个测试方法,通过运行按钮执行,就已经实现了测试用例的批量运行。
若测试用例不在一个类(文件)中,甚至不在一个包中,如何批量的来执行这些测试用例?
在 Eclipse 中批量执行测试有多种方式:
方法一
这种方式非常简单,不需要额外多写一行代码,Eclipse 本来就支持以项目或包为单位来批量执行测试用例。
- 右键点击项目—>Run As—>Run Configurations
- 弹出运行配置窗口
- 点击“Search…”按钮:
在这里你可以选择要运行的项目(mypro)或测试包(如,com.junit.test),点击“OK”回到运行配置窗口,点击“Run”按钮运行所选中范围下的所有测试用例;
方法二:测试套件
这种方法引入一种**“测试套件”**的概念,JUnit 提供了一种批量运行测试类的方法,叫测试套件。
测试套件的写法需要遵循以下原则:
- 创建一个空类作为测试套件的入口;
- 使用注解 org.junit.runner.RunWith 和 org.junit.runners.Suite.SuitClasses 修饰这个空类;
- 将 org.junit.runners.Suite 作为参数传入给注解 RunWith,以提示 Junit 为此类测试使用套件运行器执行。
- 将需要放入此测试套件的测试类组成数组作为注解 SuiteClasses 的参数;
- 保证这个空类使用 public 修饰,而且存在公开的不带任何参数的构造函数;
下面在测试包(com.junit.test)下面创建一个测试类,内容如下:
//testAll.java
package com.junit.test;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;
@RunWith(Suite.class) // 通过@RunWith()注解来执行测试套件中的测试用例
@SuiteClasses({ // 通过 SuiteClasses()注解来罗列要执行的测试类
test.class,
tests.class
})
public class testAll {
}
要执行的测试用例,如图 :
8、单元测试中的概念
(1)test case 测试用例
一个 TestCase 的实例就是一个测试用例;
- **测试用例:**就是一个完整的测试流程,包括测试前准备环境的搭建(setUp),实现测试过程的代码(run),以及测试后环境的还原(tearDown)。
- **元测试(unit test)**的本质也就在这里,一个测试用例是一个完整的测试单元,通过运行这个测试单元,可以对某一个功能进行验证。
(2)test suite 测试套件
对一个功能的验证往往是需要多测试用例的,可以把多个测试用例集合在一起执行,这就产生了测试套件 TestSuite 的概念
- 它用来组装单个测试用例
- TestSuite 也可以嵌套 TestSuite
- 可以通过@SuiteClasses 加载 TestCase 到 TestSuite 中,再返回一个 TestSuite 实例
(3)test runner 测试运行器
@RunWith 是来执行测试套件中的测试用例的。
测试的结果会保存到 TestResult 实例中,包括运行了多少测试用例,成功了多少,失败了多少,以及每条用例的耗时等信息。
(4)test fixture 测试夹具
对一个测试用例环境的搭建和销毁,是一个 fixture,通过覆盖 TestCase 的 环境的搭建setUp()和 环境的还原 tearDown()方法来实现。
- 用处:
比如说在这个测试用例中需要访问数据库,那么可以在 setUp()中建立数据库连接以及进行一些初始化,在 tearDown()中清除在数据库中产生的数据,然后关闭连接。 - 注意:
tearDown 的过程很重要,要为以后的 TestCase 留下一个干净的环境
三、用 Junit 编写 web 自动化
把单元测试的用例替换为 webdriver 自动化测试脚本,利用 Junit 的原理与特点来运行 web 自动化测试。
创建如下测试包和测试用例:
1、testBaidu.java
package com.junit.web;
import java.util.concurrent.TimeUnit;
import org.junit.*;
import static org.junit.Assert.*;
import org.openqa.selenium.*;
import org.openqa.selenium.chrome.ChromeDriver;
public class testBaidu {
private WebDriver driver;
private String baseUrl;
private StringBuffer verificationErrors = new StringBuffer();
@Before
public void setUp() throws Exception {
driver = new ChromeDriver();
baseUrl = "https://www.baidu.com/";
driver.manage().timeouts().implicitlyWait(30, TimeUnit.SECONDS);
}
@Test
public void testCase() throws Exception {
driver.get(baseUrl + "/");
driver.findElement(By.id("kw")).clear();
driver.findElement(By.id("kw")).sendKeys("junit");
driver.findElement(By.id("su")).click();
Thread.sleep(2000);
String title =driver.getTitle();
assertEquals(title,"junit_百度搜索");
}
@After
public void tearDown() throws Exception {
driver.quit();
String verificationErrorString = verificationErrors.toString();
if (!"".equals(verificationErrorString)) {
fail(verificationErrorString);
}
}
}
2、testYoudao.java
package com.junit.web;
import java.util.concurrent.TimeUnit;
import org.junit.*;
import static org.junit.Assert.*;
import org.openqa.selenium.*;
import org.openqa.selenium.chrome.ChromeDriver;
public class testYoudao {
private WebDriver driver;
private String baseUrl;
private StringBuffer verificationErrors = new StringBuffer();
@Before
public void setUp() throws Exception {
driver = new ChromeDriver();
baseUrl = "https://www.youdao.com/";
driver.manage().timeouts().implicitlyWait(30, TimeUnit.SECONDS);
}
@Test
public void testCase() throws Exception {
driver.get(baseUrl + "/");
driver.findElement(By.id("query")).clear();
driver.findElement(By.id("query")).sendKeys("webdriver");
driver.findElement(By.id("qb")).click();
Thread.sleep(2000);
String title =driver.getTitle();
assertEquals(title,"webdriver - 有道搜索");
}
@After
public void tearDown() throws Exception {
driver.quit();
String verificationErrorString = verificationErrors.toString();
if (!"".equals(verificationErrorString)) {
fail(verificationErrorString);
}
}
}
3、testAll.java
testAll.java
package com.junit.test;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;
@RunWith(Suite.class)
@SuiteClasses({
testBaidu.class,
testYoudao.class
})
public class testAll {
}