TestNG
TestNG是一个测试框架,灵感来自JUnit和NUnit。但引入了下面这些新的功能,使它更强大和更容易使用。
- 注解;
- 可在任意大的线程池运行您的测试(所有方法在它们自己的线程内,一个线程一个测试类);
- 灵活的测试配置;
- 支持数据驱动测试(@DataProvider);
- 支持参数;
- 强大的执行模型(不再使用TestSuite);
- 支持各种工具和插件(Eclipse,IDEA,Maven等…);
- 嵌入BeanShell增加进一步的灵活性;
- 默认使用JDK函数运行和日志记录(无依赖关系);
Eclipse配置TestNG
- 打开Eclipse —> Help —> Install New Software
- 然后点击Add,Name输入:TestNG,Location输入: , 点击OK。
- 勾选TestNG,一路NEXT,按提示操作。
还有一种离线安装方式,适用于网络不佳的同学,这里不给出步骤了。使用搜索引擎吧同学们。
TestNG应用到项目
安装完成后,右键你的项目Build Path —> Add Libraries,选择TestNG,点击Finish.(如果这一步没有看到TestNG,请返回上一步检查你的安装是否成功)
下面是TestNG的最简单的一个例子
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
public class TestNGDemo {
@BeforeClass
public void setUp() {
System.out.println("this is before class");
}
@Test
public void testMethod() {
System.out.println("this is TestNG test case");
}
@AfterClass
public void tearDown() {
System.out.println("this is after class");
}
}
如何使用TestNG执行测试呢?在你的类文件内代码区域或者右键类文件,Run As —> TestNG Test
TestNG注解
注解 | 描述 |
@BeforeSuite | 注解的方法将只运行一次,针对测试套件,在当前的测试套件运行前执行的方法。 |
@AfterSuite | 注解的方法将只运行一次, 针对测试套件,在当前的测试套件运行后执行的方法。 |
@BeforeClass | 注解的方法将只运行一次,在当前已经被调用的类的第一个测试方法运行前运行。 |
@AfterClass | 注解的方法将只运行一次,在当前类的所有测试方法运行完成后运行。 |
@BeforeTest | 针对测试套件, 在任何属于这个类的并且用@test标签标记的测试方法运行前运行 |
@AfterTest | 针对测试套件,在任何属于这个类的并且用@test标签标记的测试方法运行后运行 |
@BeforeGroups | 在属于这个组的第一个测试方法运行前运行 |
@AfterGroups | 在属于这个组的最后一个测试方法被调用后运行 |
@BeforeMethod | 在每个测试方法前运行 |
@AfterMethod | 在每个测试方法后运行 |
@DataProvider | 标志着提供数据的一个测试方法,注解的方法必须返回一个Object[][] |
@Factory | 标志着一个工厂方法,标记的方法将被用于返回TestNG的测试类的对象。该方法必须返回Object[]。 |
@Listeners | 定义一个测试类的监听器。 |
@Parameters | 将参数传递给@Test方法。 |
@Test | 标记一个测试类或测试方法 |
暂时不理解上面的注解没关系,在接下来的例子中将一一阐述。
执行顺序
一个小例子,让我们看下注解的执行顺序
import org.testng.annotations.Test;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.AfterTest;
import org.testng.annotations.BeforeSuite;
import org.testng.annotations.AfterSuite;
public class TestngAnnotation {
// test case 1
@Test
public void testCase1() {
System.out.println("in test case 1");
}
// test case 2
@Test
public void testCase2() {
System.out.println("in test case 2");
}
@BeforeMethod
public void beforeMethod() {
System.out.println("in beforeMethod");
}
@AfterMethod
public void afterMethod() {
System.out.println("in afterMethod");
}
@BeforeClass
public void beforeClass() {
System.out.println("in beforeClass");
}
@AfterClass
public void afterClass() {
System.out.println("in afterClass");
}
@BeforeTest
public void beforeTest() {
System.out.println("in beforeTest");
}
@AfterTest
public void afterTest() {
System.out.println("in afterTest");
}
@BeforeSuite
public void beforeSuite() {
System.out.println("in beforeSuite");
}
@AfterSuite
public void afterSuite() {
System.out.println("in afterSuite");
}
}
执行后输出
in beforeSuite
in beforeTest
in beforeClass
in beforeMethod
in test case 1
in afterMethod
in beforeMethod
in test case 2
in afterMethod
in afterClass
in afterTest
PASSED: testCase1
PASSED: testCase2
===============================================
Default test
Tests run: 2, Failures: 0, Skips: 0
===============================================
因为执行方式的问题,afterSuite没有执行,这个先不用理会。
可以看到Suite是最先执行的,接下来执行Test和Class,三种注解都只执行了一次。
因为@Test方法有两个,而Method要在每个@Test方法执行时运行,所以也执行了两次。
是不是有点小晕了,大家这样理解,@Test是整个测试的核心和主体,所有Before都是前置依赖,after则都是队尾处最后执行。
用XML管理测试
我们先来看下TestNG.xml最简单的一个配置文件
该文件可以放在项目src/目录下。右键执行上面的文件,跟执行类文件同样效果,且afterSuite也会被执行。
结构分别是suite、test、class、groups和method层次,看到这里应该明白注解里的BeforeSuite、BeforeTest、BeforeClass、BeforeGroups、BeforeMethod等对应的是什么了吧。
suite name和test name都是自定义名字,但class name需要填写实际的测试类,记得要加包名。
文件节点属性说明
suite属性说明:
@name: suite的名称,必须参数
@junit:是否以Junit模式运行,可选值(true | false),默认"false"
@verbose:命令行信息打印等级,不会影响测试报告输出内容;可选值(1|2|3|4|5)
@parallel:是否多线程并发运行测试;可选值(false | methods | tests | classes | instances),默认 "false"
@thread-count:当为并发执行时的线程池数量,默认为"5"
@configfailurepolicy:一旦Before/After Class/Methods这些方法失败后,是继续执行测试还是跳过测试;可选值 (skip | continue),默认"skip"
@annotations:获取注解的位置,如果为"javadoc", 则使用javadoc注解,否则使用jdk注解
@time-out:为具体执行单元设定一个超时时间,具体参照parallel的执行单元设置;单位为毫秒
@skipfailedinvocationcounts:是否跳过失败的调用,可选值(true | false),默认"false"
@data-provider-thread-count:并发执行时data-provider的线程池数量,默认为"10"
@object-factory:一个实现IObjectFactory接口的类,用来实例测试对象
@allow-return-values:是否允许返回函数值,可选值(true | false),默认"false"
@preserve-order:顺序执行开关,可选值(true | false) "true"
@group-by-instances:是否按实例分组,可选值(true | false) "false"
test属性说明:
@name:test的名字,必选参数;测试报告中会有体现
@junit:是否以Junit模式运行,可选值(true | false),默认"false"
@verbose:命令行信息打印等级,不会影响测试报告输出内容;可选值(1|2|3|4|5)
@parallel:是否多线程并发运行测试;可选值(false | methods | tests | classes | instances),默认 "false"
@thread-count:当为并发执行时的线程池数量,默认为"5"
@annotations:获取注解的位置,如果为"javadoc", 则使用javadoc注解,否则使用jdk5注解
@time-out:为具体执行单元设定一个超时时间,具体参照parallel的执行单元设置;单位为毫秒
@enabled:设置当前test是否生效,可选值(true | false),默认"true"
@skipfailedinvocationcounts:是否跳过失败的调用,可选值(true | false),默认"false"
@preserve-order:顺序执行开关,可选值(true | false) "true"
@group-by-instances:是否按实例分组,可选值(true | false) "false"
@allow-return-values:是否允许返回函数值,可选值(true | false),默认"false"
更多testng配置及说明,请移步
分组
看下面的小例子
import org.testng.annotations.AfterGroups;
import org.testng.annotations.BeforeGroups;
import org.testng.annotations.Test;
public class TestngGroups {
@BeforeGroups(groups = { "group1"})
public void beforeGroups() {
System.err.println("in before group1");
}
@AfterGroups(groups = { "group1"})
public void afterGroups() {
System.err.println("in after group1");
}
@Test(groups = { "group1", "group2" })
public void testMethod1() {
System.err.println("groups = { group1, group2 }");
}
@Test(groups = { "group1" })
public void testMethod2() {
System.err.println("groups = { group1 }");
}
@Test(groups = { "group2" })
public void testMethod3() {
System.err.println("groups = { group2 }");
}
}
用xml来管理测试
执行后输出
in before group1
groups = { group1, group2 }
groups = { group1 }
in after group1
===============================================
Suite
Total tests run: 2, Failures: 0, Skips: 0
===============================================
可以看到我们共有三个@Test,一个属于group1一个属于group2 ,还有一个同属于group1, group2。
然后我们在xml文件中groups节点下,只run了分组group1,所以并没有执行属于group2的那个@Test
参数化
在大多数情况下,你会遇到这样一个场景,业务逻辑需要一个巨大的不同数量的测试。
参数测试,允许测试人员一遍又一遍使用不同的值,运行同样的测试。TestNG有两种不同的方式让你直接传递参数:
使用testng.xml
数据提供程序
使用testng.xml
在testng.xml文件中定义参数,然后在源文件中引用这些参数。让我们看看下面的例子中如何使用这种技术来传递参数。
创建一个类文件,如下:
public class ParameterizedTest1 {
@Test
@Parameters("myName")
public void parameterTest(String myName) {
System.out.println("Parameterized value is : " + myName);
}
}
创建 testng.xml,如下:
执行后输出
Parameterized value is : sun
===============================================
Suite
Total tests run: 1, Failures: 0, Skips: 0
===============================================
先看下testng.xml,可以看到test节点下有一个parameter子节点,这个就是定义参数的位置。
name自定义,但你在代码处引用时需要记住你写的name,value填写你要传的值。 当然我们也可以把parameter节点放在suite下,意味着该参数在整个suite下的所有test都是共享的。TestNG 对testng.xml 的参数的类型指定的值会自动尝试转换。下面是支持的类型:
* String
* int/Integer
* boolean/Boolean
* byte/Byte
* char/Character
* double/Double
* float/Float
* long/Long
* short/Short
数据提供程序
当你需要更复杂的参数,在这种情况下,你可以使用数据提供程序。用@DataProvider批注,这个注解只有一个字符串属性:它的名字。如果不提供名称,数据提供程序的名称会默认为方法的名称。数据提供程序返回一个对象数组。
让我们来看下面的例子:
public class ParamTestWithDataProvider1 {
@DataProvider(name = "params")
public static Object[][] primeNumbers() {
return new Object[][] { {1, 2 }, {2, 2}, {3, 4}, {4, 4}, {5, 6}};
}
@Test(dataProvider = "params")
public void testPrimeNumberChecker(Integer inputNumber1, Integer inputNumber2) {
if (inputNumber1 == inputNumber2) {
System.out.println("相等的 [" + inputNumber1 + " " + inputNumber2 + "]");
} else {
System.out.println("不相等的 [" + inputNumber1 + " " + inputNumber2 + "]");
}
}
}
创建 testng.xml,如下:
执行后输出
不相等的 [1 2]
相等的 [2 2]
不相等的 [3 4]
相等的 [4 4]
不相等的 [5 6]
PASSED: testPrimeNumberChecker(1, 2)
PASSED: testPrimeNumberChecker(2, 2)
PASSED: testPrimeNumberChecker(3, 4)
PASSED: testPrimeNumberChecker(4, 4)
PASSED: testPrimeNumberChecker(5, 6)
===============================================
Default test
Tests run: 5, Failures: 0, Skips: 0
===============================================
DataProvider共提供了五组数据,供测试方法testPrimeNumberChecker来调用。
来看输出结果,每组数据依次调用了测试方法,共执行了五次。 上面的示例就是最简单的数据驱动测试,用定义的数据来控制测试行为和业务逻辑。异常测试
测试中,有时候我们期望某些代码抛出异常。TestNG 为异常处理代码提供了一个选项,可以选择是否需要代码抛出异常或者不抛出。TestNG通过@Test(expectedExceptions) 来判断期待的异常,并且判断异常Message
来看下面的例子:
import org.testng.annotations.Test;
public class ExceptionDemo {
@Test(expectedExceptions = ArrayIndexOutOfBoundsException.class,
expectedExceptionsMessageRegExp = "IndexOutOfBounds")
public void testException(){
throw new ArrayIndexOutOfBoundsException("IndexOutOfBounds");
}
}
执行后输出
PASSED: testException
===============================================
Default test
Tests run: 1, Failures: 0, Skips: 0
===============================================
我们抛出了ArrayIndexOutOfBoundsException异常,并且在expectedExceptions的value里声明了期待的异常 ArrayIndexOutOfBoundsException.class.
抛出的异常等于期待的异常,并且ExceptionMessage也相符。达到了我们的测试目的。我们稍微改动一下上面的例子
import org.testng.annotations.Test;
public class ExceptionDemo {
@Test(expectedExceptions = ArrayIndexOutOfBoundsException.class,
expectedExceptionsMessageRegExp = "IndexOutOfBounds")
public void testException(){
throw new ArrayIndexOutOfBoundsException("IndexOutOf");
}
}
执行后输出
FAILED: testException
org.testng.TestException:
Expected exception java.lang.ArrayIndexOutOfBoundsException but got org.testng.TestException:
The exception was thrown with the wrong message: expected "IndexOutOfBounds" but got "IndexOutOf"
at org.testng.internal.Invoker.handleInvocationResults(Invoker.java:1497)
at org.testng.internal.Invoker.invokeTestMethods(Invoker.java:1245)
at org.testng.internal.TestMethodWorker.invokeTestMethods(TestMethodWorker.java:127)
at org.testng.internal.TestMethodWorker.run(TestMethodWorker.java:111)
at org.testng.TestRunner.privateRun(TestRunner.java:767)
at org.testng.TestRunner.run(TestRunner.java:617)
at org.testng.SuiteRunner.runTest(SuiteRunner.java:334)
at org.testng.SuiteRunner.runSequentially(SuiteRunner.java:329)
at org.testng.SuiteRunner.privateRun(SuiteRunner.java:291)
at org.testng.SuiteRunner.run(SuiteRunner.java:240)
at org.testng.SuiteRunnerWorker.runSuite(SuiteRunnerWorker.java:52)
at org.testng.SuiteRunnerWorker.run(SuiteRunnerWorker.java:86)
at org.testng.TestNG.runSuitesSequentially(TestNG.java:1224)
at org.testng.TestNG.runSuitesLocally(TestNG.java:1149)
at org.testng.TestNG.run(TestNG.java:1057)
at org.testng.remote.RemoteTestNG.run(RemoteTestNG.java:111)
at org.testng.remote.RemoteTestNG.initAndRun(RemoteTestNG.java:204)
at org.testng.remote.RemoteTestNG.main(RemoteTestNG.java:175)
...... 此处省略若干报错信息
===============================================
Default test
Tests run: 1, Failures: 1, Skips: 0
===============================================
可以看到我们抛出的异常和期待的异常是相符的。
而ExceptionsMessage我们抛出了"IndexOutOf",期待的却是 "IndexOutOfBounds",所以测试失败。没有应用到实际的自动化测试中,大家可能会感觉到迷惑。在前面的selenium webdriver练习中,你们应该会经常遇到一个异常:NoSuchElementException(没有找到元素). 现在大家设想这么一个场景,我们要测试某个页面的一个按钮不应该显示,如果显示则测试失败。那么测试就可以像下面这样写:
public class ExceptionDemo {
@Test(expectedExceptions = NoSuchElementException.class)
public void buttonIsNotDisplayed() {
WebElement button = driver.findElement(By.id("button"));
button.click();
}
}
Assert/Log/Report
Assert:用来校验测试的结果。
Log: 类似于Log4j的log记录。Report: 输出本次测试的结果报告。下面只讲下简单的使用:
Assert
常用方法介绍:
fail 直接失败测试用例,可以抛出异常。
assertTrue 判断是否为true。
assertFalse 判断是否为false。
assertSame 判断引用地址是否相等。
assertNotSame 判断引用地址是否不相等。
assertNull 判断是否为null
assertNotNull 判断是否不为null
assertEquals 判断是否相等,Object类型的对象需要实现hashCode及equals方法,集合类型Collection/Set/Map 中的对象也需要实现hashCode及equals方法,3个double参数时比较好玩,前两个double相等,或者前两个double的差值小于传入的第三个double值,即偏移量小于多少时,认为相等。
assertNotEquals 判断是否不相等
assertEqualsNoOrder 判断忽略顺序是否相等
例子
public class AssertDemo {
public void assertName(String name) {
Assert.assertEquals(name, "sun", "姓名不相符,请检查。");
}
@Test
public void checkName() {
assertName("haha");
}
}
示例是assertEquals(String actual, String expected, String message)方法,判断两个值是否相等。
来看方法的参数,第一个是实际值,第二个是预期值,第三个message意思是如果不相等则需要提示的信息。 我们预期的值是“sun”,但传入了“haha”,所以测试是失败的。输出结果
FAILED: checkName
java.lang.AssertionError: 姓名不相符,请检查。 expected [sun] but found [haha]
at org.testng.Assert.fail(Assert.java:94)
at org.testng.Assert.failNotEquals(Assert.java:494)
at org.testng.Assert.assertEquals(Assert.java:123)
at org.testng.Assert.assertEquals(Assert.java:176)
at com.sun.example.AssertDemo.assertName(AssertDemo.java:9)
at com.sun.example.AssertDemo.checkName(AssertDemo.java:14)
......省略若干
===============================================
Default test
Tests run: 1, Failures: 1, Skips: 0
===============================================
合理的使用Assert会上你的测试更健壮,直观,易维护,定位错误更快速。
其它的Assert方法大同小异,不再多做介绍。Log
跟Java最知名的Log4j很类似
首先在工程src/下新建一个log4testng.properties文件,内容为:
log4testng.debug=true
log4testng.rootLogger=DEBUG
log4testng.logger.org.testng.reporters.EmailableReporter=TRACE
log4testng.logger.org.testng=WARN
用了最简单的log规则,还可以添加很多定制内容,不多做介绍。
下面在代码中应用
public class LoggerDemo {
public static Logger logger = Logger.getLogger(LoggerDemo.class);
@Test
public void loggerDemo() {
logger.debug("这是调试信息");
logger.info("测试是通过的");
logger.warn("这是一个警告");
logger.error("出现一个错误");
}
}
执行后输出
[LoggerDemo] [DEBUG] 这是调试信息
[LoggerDemo] [INFO] 测试是通过的
[LoggerDemo] [WARN] 这是一个警告
[LoggerDemo] [ERROR] 出现一个错误
PASSED: loggerDemo
===============================================
Default test
Tests run: 1, Failures: 0, Skips: 0
===============================================
如果想要输出结果更加详细,可以在log4testng.properties文件内定制规则。 还可以让日志显示时间,测试方法,代码行等等,如下面用log4j输出的一条log:
[INFO ] 2016-06-17 10:29:55 method:com.allinmd.util.AppiumServer.iOSDriverRun(AppiumServer.java:164)
swipe欢迎页
跟Assert相同,合理的使用Log,可以让你的错误定位更明确。
Report
TestNG自带的报告模板略丑,不忍直视。所以采用了另外一个开源的项目ReportNG, 它是TestNG的一个HTML报表生成插件,用于替换TestNG默认的HTML报表。ReportNG提供一种简单的方式来查看测试结果,并能够对结 果代码进行着色。还可以通过修改CSS文件来替换默认的输出样式,修改源码改变模板。
第一步
第二步导入到项目,这个就不用重复了吧,忘了的自己面壁思过。 第三步在TestNG.xml文件配置listener监听:输出报告示例
UI自动化测试用例实践
下面是唯医用户登录的例子,运用了TestNG里面的一些方法。
public class AllinLoginDemo {
private WebDriver driver;
public static Logger logger = Logger.getLogger(AllinLoginDemo.class);
/**
* 启动一个ChromeDriver实例,并链接到唯医首页
*/
@BeforeClass
public void setUp() {
driver = new ChromeDriver();
driver.manage().window().maximize();
driver.manage().timeouts().implicitlyWait(20, TimeUnit.SECONDS);
driver.get("http://www.allinmd.cn");
}
/**
* 唯医登录
*/
@Test (priority = 1)
public void allinLogin() {
logger.info("开始唯医用户登录");
WebElement username = driver.findElement(By.xpath(".//*[@id='allinLogin']/div[1]/div[3]/input"));
username.sendKeys("17700020000");
WebElement password = driver.findElement(By.xpath(".//input[@placeholder='你的密码']"));
password.sendKeys("testuser111111");
WebElement login_button = driver.findElement(By.xpath("//*[@id='allinLogin']/div/div[7]/div"));
login_button.click();
}
/**
* 检查登录后跳转
*/
@Test (priority = 2)
public void checkLogin() {
waitDownPage("我的首页-唯医,allinmd", 15);
Assert.assertEquals(driver.getTitle(), "我的首页-唯医,allinmd", "没有跳转到首页,请检查是否登录成功。");
logger.info("唯医用户登录成功");
}
/**
* 关闭浏览器,并退出driver实例
*/
@AfterClass
public void tearDown() {
driver.quit();
}
/**
* 等待页面跳转
* 直到当前title不等于传入的title或者超时
* @param waitPageTitle
*/
public void waitDownPage(String currenTitle, int timeout) {
int num = 0;
while(!driver.getTitle().contains(currenTitle) && num < timeout) {
sleep(1);
num ++;
}
}
/**
* Thread.sleep
* @param d
*/
public void sleep(double d) {
try {
d *= 1000;
Thread.sleep((int)d);
} catch(Exception e) {}
}
}
上面是一个唯医登录的自动化测试用例,现在假设还有个唯医注册的自动化测试用例。
我们testng.xml就可以像下面这样写:执行后输出
[AllinLoginDemo] [INFO] 开始唯医用户登录
[AllinLoginDemo] [INFO] 唯医用户登录成功
===============================================
Suite
Total tests run: 2, Failures: 0, Skips: 0
===============================================