在前面的文章中详细的演示了TestNG测试框架的安装以及基本的应用,和testng.xml配置文件的应用,在本次文章中系统详细的概述TestNG框架中的注释,在Python里面这样的注释可以理解为装饰器。这些知识点主要涉及具体为:测试前和测试后,参数化,注释测试,禁用测试,异常测试,时间测试,以及把测试数据传递到测试方法中。下面结合具体的实际案例和具体的案例实战,从各个不同维度来演示各个知识点的应用。在Java5中引入了注释的功能,比如一个类集成了Thread类,在编写run方法的时候就会引入@Override,当然还有其他的案例。在TestNG的框架中,更多体现在测试执行前和测试执行后,我们在讲解单元测试框架的时候说过,一个完整的测试框架,它首先就得具备测试执行前的初始化以及测试执行后的环境清理。在TestNG框架中,这些点主要会包含在针对类,以及针对测试方法。我们先来看Before和After的应用,也就是说测试套件,测试类,测试用例,测试方法,具体案例源码如下:
代码语言:javascript复制package org.ui.selenium.com;
import org.junit.Before;
import org.testng.annotations.*;
import org.testng.Assert;
public class AnnotationTest
{
@BeforeSuite
public void beforeSuite()
{
System.out.println("申明执行前的测试套件");
}
@AfterSuite
public void afterSuite()
{
System.out.println("申明执行后的测试套件");
}
@BeforeTest
public void beforeTest()
{
System.out.println("申明执行前的测试方法");
}
@AfterTest
public void afterTest()
{
System.out.println("申明执行后的测试方法");
}
@BeforeClass
public void beforeClass()
{
System.out.println("申明执行前的测试类");
}
@AfterClass
public void afterClass()
{
System.out.println("申明执行后的测试类");
}
@BeforeGroups(groups = "testOne")
public void beforeGroupOne()
{
System.out.println("申明执行前的测试组testOne");
}
@AfterGroups(groups = "testOne")
public void afterGroupOne()
{
System.out.println("申明执行前的测试组testOne");
}
@BeforeGroups(groups = "testOne")
public void beforeGroupTwo()
{
System.out.println("申明执行前的测试组testTwo");
}
@AfterGroups(groups = "testOne")
public void afterGroupTwo()
{
System.out.println("申明执行前的测试组testTwo");
}
@BeforeMethod
public void setUp()
{
System.out.println("初始化环境");
}
@AfterMethod
public void tearDown()
{
System.out.println("清理环境");
}
@Test(groups = {"testOne"})
public void testOneMethod()
{
System.out.println("test one method");
}
@Test(groups = {"testTwo"})
public void testTwoMethod()
{
System.out.println("test two method");
}
}
执行后的结果信息输出,如下所示:
代码语言:javascript复制[TestNG] Running:
/Applications/code/workSpace/ngApp/testng.xml
申明执行前的测试套件
申明执行前的测试方法
申明执行前的测试类
申明执行前的测试组testOne
申明执行前的测试组testTwo
初始化环境
test one method
清理环境
申明执行前的测试组testTwo
申明执行前的测试组testOne
初始化环境
test two method
清理环境
申明执行后的测试类
申明执行后的测试方法
申明执行后的测试套件
===============================================
Sample Suite
Total tests run: 2, Failures: 0, Skips: 0
===============================================
从执行后的测试结果信息来看,我们能够看到具体需要执行一个测试用例的方法,会有很多的前置方法和后置方法,当然这些前置以及后置的方法都是实际过程中需要的。事实上,如果您熟悉unittest测试框架的话,就应该很清楚,这样的方法我们称为测试固件,也就是测试前置和测试后置,在前置和后置的工作中,其实我们真正需要的是执行一个测试方法前的初始化环境和执行后的清理环境,比如编写一个UI的自动化测试用例,那么前置工作就是首先需要初始化类以及打开浏览器并且显示到目标地址,后置工作就是关闭浏览器,如下案例代码,就显示出了这样很标准的一个方式:
代码语言:javascript复制package com.selenium.ui.org;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import org.testng.Assert;
public class BingTest
{
WebDriver driver=null;
@BeforeMethod
public void setUp()
{
driver=new ChromeDriver();
driver.manage().window().maximize();
driver.get("https://cn.bing.com/");
}
@Test
public void test_bing_url()
{
Assert.assertEquals(driver.getCurrentUrl(),"https://cn.bing.com/");
}
@AfterMethod
public void tearDown()
{
driver.quit();
}
}
习惯行的,我们在每个测试方法前加@Test,这样的目的是告诉测试框架,这个一个测试用例的方法,当然更加规范的标准是建议每个测试用例都需要以test开头,这样别人看了后,就立刻知道这是一个测试的方法。我们也可以在每个测试方法添加描述信息,也就是测试用例的描述信息,这样在后期排查错误中就能够清晰的知道这个测试用例是测试那个测试点,如果没有这些信息可能需要看代码才能够知道这个测试用例是测试什么的,具体案例代码如下:
代码语言:javascript复制package com.selenium.ui.org;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import org.testng.Assert;
public class BingTest
{
WebDriver driver=null;
@BeforeMethod
public void setUp()
{
driver=new ChromeDriver();
driver.manage().window().maximize();
driver.get("https://cn.bing.com/");
}
@Test(description = "验证bing搜索的首页URL地址")
public void test_bing_url()
{
Assert.assertEquals(driver.getCurrentUrl(),"https://cn.bing.com/");
}
@AfterMethod
public void tearDown()
{
driver.quit();
}
}
在实际工作中,可能编写的有点测试用例到最后并不需要执行,可能是这个功能点暂时取消,可能是其他原因,总之在一个测试类里面,我们需要提供一个测试用例的方法不被执行的功能点,也就是enabled,为true是执行,为false是不执行,案例代码如下所示:
代码语言:javascript复制package org.ui.selenium.com;
import org.testng.annotations.Test;
import org.testng.Assert;
public class IndexTest
{
@Test
public void test_index_one()
{
System.out.println("第一个测试用例");
}
@Test
public void test_index_two()
{
System.out.println("第二个测试用例");
}
@Test(enabled = false)
public void test_index_three()
{
System.out.println("第三个测试用例");
}
}
执行该测试类后,第三个测试用例就不会被执行,也就是禁用该测试用例执行。
异常测试点的测试,在一个功能点的测试中,我们不仅仅需要思考正确功能点的测试,还需要异常功能点的测试,比如两个数相除,分母为0,就需要有异常的测试,在TestNG的测试框架中也提供了异常的测试,也就是期望符合的异常结果信息,比如分母是0,我们期望报XX的异常,那么程序报了这个异常,说明符合我们的预期结果信息。下面就以一个具体的案例来演示这部分的应用,测试代码如下所示:
代码语言:javascript复制package test.ui.general;
import general.Division;
import org.testng.annotations.Test;
import org.testng.Assert;
public class DivisionTest
{
Division objDiv=null;
@Test(description = "测试1处1的结果信息")
public void test_ui_one()
{
objDiv=new Division(1,1);
Assert.assertEquals(objDiv.getDivision(),1.0);
}
@Test(description = "分母为0的异常情况处理",expectedExceptions = {ArithmeticException.class})
public void test_ui_two()
{
objDiv=new Division(1,0);
System.out.println(objDiv.getDivision());
}
}
下面我们来看依赖性的测试,在业务场景的测试中,经常会遇到前一个测试用例的执行是后一个测试用例的输入,也就是说第一个测试用例的结果信息是第二个测试用例的输入,在接口测试中这样的情况是比较场景,依赖在TestNG使用的关键字是dependsOnMethods,我们通过一个UI自动化测试的案例来演示这部分的具体应用,案例代码如下:
代码语言:javascript复制package com.selenium.ui.org;
import org.openqa.selenium.By;
import org.openqa.selenium.WebElement;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import org.testng.Assert;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
public class BaiduTwoTest
{
WebDriver driver;
@BeforeMethod
public void setUp()
{
driver=new ChromeDriver();
driver.manage().window().maximize();
}
@Test(description = "导航到百度首页")
public void test_baidu_one()
{
driver.get("http://www.baidu.com");
}
@Test(description = "百度首页搜索关键字",dependsOnMethods = {"test_baidu_one"})
public void test_baidu_so() throws InterruptedException
{
driver.get("http://www.baidu.com");
Thread.sleep(3000);
WebElement element=driver.findElement(By.id("kw"));
element.sendKeys("无涯课堂");
Assert.assertEquals(element.getAttribute("value"),"无涯课堂");
}
@AfterMethod
public void tearDown()
{
driver.quit();
}
}
依据如上的案例可以看到,这样执行就会有顺序了。这个得具体根据实际的业务诉求在执行中来决定,TestNG框架只是给我们提供了一个思想,或者说是一种能力,具体怎么使用,还得结合具体的业务场景来使用。
测试用例在执行的时候,可能由于网络等情况,导致超时,在接口测试中,同步交互经常会涉及到这些,而在UI自动化测试中,更多的是客户端的资源没有加载出来,导致元素定位失败等情况。针对这些超时堵塞的情况,在TestNG的测试框架中,配置允许等待测试完全执行,再更加具体的说就是在一个配置的时间范围内,都是可以处于等待的情况,如果超过这个时间范围内,程序依然会报错,这很好理解的,超时机制是任何一个应用程序都需要考虑到的。配置的方法有两种策略,分布是测试套件级别以及测试方法级别,针对这两种情况我们都可以看看,涉及到的套件级别可以配置在testng.xml的配置文件中,修改后的testng.xml文件内容如下:
代码语言:javascript复制<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
<!--<suite name="UnitSuite">-->
<suite name="Sample Suite" time-out="1000">
<test name="Method Test">
<classes>
<class name="com.selenium.ui.org.BaiduTest"></class>
</classes>
</test>
</suite>
再来看针对测试方法级别的超时配置,套件级别是针对所有的测试用例,而测试方法级别的,主要是针对单个测试方法而言的,具体还得看情况,测试方法级别的源码具体为:
代码语言:javascript复制package com.selenium.ui.org;
import org.openqa.selenium.By;
import org.openqa.selenium.WebElement;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import org.testng.Assert;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
public class BaiduTest
{
WebDriver driver=null;
@BeforeMethod
public void setUp()
{
driver=new ChromeDriver();
driver.get("https://www.baidu.com/");
}
@Test(description = "验证百度首页的title信息准确性")
public void test_baidu_title()
{
Assert.assertEquals(driver.getTitle(),"百度一下,你就知道");
}
@Test(description = "验证百度首页的URL信息")
public void test_baidu_url()
{
Assert.assertEquals(driver.getCurrentUrl(),"https://www.baidu.com/");
}
@Test(description = "验证搜索的关键字的值内容",timeOut = 5)
public void test_so_value()
{
WebElement element=driver.findElement(By.id("kw"));
String str1="无涯课堂为您服务!";
element.sendKeys(str1);
Assert.assertEquals(element.getAttribute("value"),str1);
}
@AfterMethod
public void tearDown()
{
driver.quit();
}
}
可以看到最后一个测试用例给的时间是5毫秒,预计执行会失败,我们执行测试用例,执行后会报ThreadTimeoutException的异常信息。所以如果在实际执行中遇到该错误的信息,那么下来需要处理的是就是在具体的测试用例上加上超时处理的机制,这样测试用例执行就不会因为超时的情况导致测试点失败。
参数化在测试中占据非常重要的位置,更加专业的说是数据驱动,参数化的本质是把测试数据的对象放到一个列表中,然后针对列表里面的对象进行循环赋值,这样的一个过程,就是参数化的本质思想。在TestNG框架中可以围绕两个维度开进行,一是testng.xml配置文件,另外一种方式是数据提供器,我们先来看第一种的方式,涉及测试源码为:
代码语言:javascript复制package com.selenium.ui.org;
import org.openqa.selenium.By;
import org.openqa.selenium.WebElement;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Parameters;
import org.testng.annotations.Test;
import org.testng.Assert;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
public class BaiduTwoTest
{
WebDriver driver;
@BeforeMethod
public void setUp()
{
driver=new ChromeDriver();
driver.manage().window().maximize();
}
@Parameters({"soKeywordData"})
@Test
public void test_baidu_so(String soKeywordData) throws InterruptedException
{
driver.get("http://www.baidu.com");
WebElement element=driver.findElement(By.id("kw"));
element.sendKeys(soKeywordData);
Thread.sleep(5000);
Assert.assertEquals(element.getAttribute("value"),soKeywordData);
}
@AfterMethod
public void tearDown()
{
driver.quit();
}
}
测试配置文件testng.xml的文件内容为:
代码语言:javascript复制<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
<!--<suite name="UnitSuite">-->
<suite name="Sample Suite">
<test name="Method Test">
<parameter name="soKeywordData" value="TestNG实战"></parameter>
<classes>
<class name="com.selenium.ui.org.BaiduTwoTest"></class>
</classes>
</test>
</suite>
执行后,会把testng.xml里面的测试数据赋值给测试方法,这样的一种方法就是通过testng.xml配置文件来实现初始化的方式,下面再来看数据提供器的方式,数据提供器就会使用到DataProvider,TestNG 提供的重要功能之一是数据提供器功能。它帮助用户编写数据驱动的测试,这意味着相同的测试方法可以使用不同的数据集运行多次。DataProvider 是将参数传递到测试方法的第二种方法。它有助于为测试方法提供复杂的参数,因为不可能从 XML 做到这一点。若要在测试中使用 DataProvider 功能,必须声明由 DataProvider 注释的方法,然后在测试注释中的 DataProvider 属性使用测试方法中的该方法。让我们编写一个简单的示例,了解如何在测试中使用 DataProvider 功能。下面还是通过具体的案例来说明它的应用,涉及到的案例代码如下:
代码语言:javascript复制package com.selenium.ui.org;
import org.openqa.selenium.By;
import org.openqa.selenium.WebElement;
import org.testng.annotations.*;
import org.testng.Assert;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
public class BaiduTwoTest
{
WebDriver driver;
@BeforeMethod
public void setUp()
{
driver=new ChromeDriver();
driver.manage().window().maximize();
}
@DataProvider(name = "dataProvider")
public Object[][] dataProviderMethod()
{
return new Object[][]{{"TestNG实战"},{"Pytest实战"},{"unittest实战"},{"Java实战"}};
}
@Test(dataProvider = "dataProvider")
public void test_baidu_so(String soKeywordData) throws InterruptedException
{
driver.get("http://www.baidu.com");
WebElement element=driver.findElement(By.id("kw"));
element.sendKeys(soKeywordData);
Thread.sleep(5000);
Assert.assertEquals(element.getAttribute("value"),soKeywordData);
}
@AfterMethod
public void tearDown()
{
driver.quit();
}
}
执行的时候,会从Object的列表中对参数的值进行循环,依次赋值,然后传递给测试方法,所以上面的测试用例执行后,会执行四个测试用例,因为我们给了四个不同的测试数据,这个思想也是符合参数化的本质设计思想的,执行后的结果信息如下所示:
所以在参数化中,我个人更加建议使用数据提供器的方式,这样更加高效,当然在实际的场景中,还是要根据具体的业务形态来决定使用那种方式。当然实际的应用中,我们可以把数据提供器的类和方法单独的分离出来,分离出去后它的源码为:
代码语言:javascript复制package com.selenium.ui.org;
import org.testng.annotations.DataProvider;
public class DataClass
{
@DataProvider(name = "dataProvider")
public static Object[][] dataProviderMethod()
{
return new Object[][]
{
{"TestNG实战"},
{"Pytest实战"},
{"unittest实战"},
{"Java实战"}
};
}
}
修改后的测试代码为:
代码语言:javascript复制package com.selenium.ui.org;
import org.openqa.selenium.By;
import org.openqa.selenium.WebElement;
import org.testng.annotations.*;
import org.testng.Assert;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
public class BaiduTwoTest
{
WebDriver driver;
@BeforeMethod
public void setUp()
{
driver=new ChromeDriver();
driver.manage().window().maximize();
}
@Test(dataProvider = "dataProvider",dataProviderClass =DataClass.class)
public void test_baidu_so(String soKeywordData) throws InterruptedException
{
driver.get("http://www.baidu.com");
WebElement element=driver.findElement(By.id("kw"));
element.sendKeys(soKeywordData);
Thread.sleep(3000);
Assert.assertEquals(element.getAttribute("value"),soKeywordData);
}
@AfterMethod
public void tearDown()
{
driver.quit();
}
}