第二章 TestNG的注解(一)- 生命周期
TestNG框架编写case一般是三个步骤:
在第1步里,我们通常可以见到以下这些和生命周期相关的注解来帮助我们完成case:
注解 | 作用 |
---|---|
@BeforeSuite | 使用此注解的方法将在该套件中所有测试运行之前运行。 |
@AfterSuite | 使用此注解的方法将在运行此套件中所有测试之后运行。 |
@BeforeTest | 使用此注解的方法将在运行任何属于标记内的类的测试方法之前运行。 |
@AfterTest | 使用此注解的方法将在所有属于标记内的类的测试方法运行后运行。 |
@BeforeGroups | 此配置方法将在其之前运行的组的列表。保证此方法可以在调用属于任何一个组的第一个测试方法之前不久运行。 |
@AfterGroups | 此配置方法将在其后运行的组的列表。保证在调用属于这些组中任何一个的最后一个测试方法后不久便可以运行该方法。 |
@BeforeClass | 使用此注解的方法将在调用当前类中的第一个测试方法之前运行。 |
@AfterClass | 使用此注解的方法将在当前类中的所有测试方法运行之后运行。 |
@BeforeMethod | 使用此注解的方法将在每个测试方法之前运行。 |
@AfterMethod | 使用此注解的方法将在每种测试方法之后运行。 |
这些注解大致可以分为两种类型,及before类和after类,before是在测试方法执行前运行,可以是一些初始化、数据准备操作,after则是在测试方法执行后运行,可以是一些销毁和关闭动作,例如在使用selenium进行UI自动化测试时可以用@BeforeClass注解来设置要启动浏览器和初始化配置,使用@AfterClass注解来退出浏览器。
我们可以通过下面的例子来看下对应注解执行的时机:
package org.example.demo;
import org.testng.annotations.*;
public class OrderTest {
@BeforeSuite
public void beforeSuite() {
System.out.println("@BeforeSuite...");
}
@AfterSuite
public void afterSuite() {
System.out.println("@AfterSuite...");
}
@BeforeTest
public void beforeTest() {
System.out.println("@BeforeTest...");
}
@AfterTest
public void afterTest() {
System.out.println("@AfterTest...");
}
@BeforeGroups
public void beforeGroups() {
System.out.println("@BeforeGroups...");
}
@AfterGroups
public void afterGroups() {
System.out.println("@AfterGroups...");
}
@BeforeClass
public void beforeClass() {
System.out.println("@BeforeClass...");
}
@AfterClass
public void afterClass() {
System.out.println("@AfterClass...");
}
@BeforeMethod
public void beforeMethod() {
System.out.println("@BeforeMethod...");
}
@AfterMethod
public void afterMethod() {
System.out.println("@AfterMethod...");
}
@Test
public void test() {
System.out.println("@Test...");
}
}
运行结果:
@BeforeSuite...
@BeforeTest...
@BeforeClass...
@BeforeMethod...
@Test...
@AfterMethod...
@AfterClass...
@AfterTest...
@AfterSuite...
由此可以得出执行的顺序:
@BeforeSuite->@BeforeGroups->@BeforeClass->@BeforeTest->@BeforeMethod
@AfterMethod->@AfterTest->@AfterClass->@AfterGroups->@AfterSuite
...
...
@Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
@Target({
METHOD, TYPE, CONSTRUCTOR})
public @interface Test {
...
...
@Test是TestNG最为核心的注解,其作用是将类或方法标记为测试的一部分。@Test注解的作用目标可以是方法也可以是类(这里居然还有能作用在构造器上有点不明所以,本人暂时还没遇到过这样的使用场景这里就不探讨了)。
...
...
while (null != cls) {
boolean hasClassAnnotation = isAnnotationPresent(annotationFinder, cls, annotationClass);
Method[] methods = cls.getDeclaredMethods();
for (Method m : methods) {
boolean hasMethodAnnotation = isAnnotationPresent(annotationFinder, m, annotationClass);
boolean hasTestNGAnnotation =
isAnnotationPresent(annotationFinder, m, IFactoryAnnotation.class) ||
isAnnotationPresent(annotationFinder, m, ITestAnnotation.class) ||
isAnnotationPresent(annotationFinder, m, CONFIGURATION_CLASSES);
boolean isPublic = Modifier.isPublic(m.getModifiers());
if ((isPublic && hasClassAnnotation && (! hasTestNGAnnotation)) || hasMethodAnnotation) {
// Small hack to allow users to specify @Configuration classes even though
// a class-level @Test annotation is present. In this case, don't count
// that method as a @Test
if (isAnnotationPresent(annotationFinder, m, IConfigurationAnnotation.class) &&
isAnnotationPresent(annotationFinder, cls, ITestAnnotation.class))
{
Utils.log("", 3, "Method " + m + " has a configuration annotation"
+ " and a class-level @Test. This method will only be kept as a"
+ " configuration method.");
continue;
}
// Skip the method if it has a return type
if (m.getReturnType() != void.class && ! xmlTest.getAllowReturnValues()) {
Utils.log("", 2, "Method " + m + " has a @Test annotation"
+ " but also a return value:"
+ " ignoring it. Use to fix this" );
continue;
}
String key = createMethodKey(m);
if (null == vResult.get(key)) {
ITestNGMethod tm = new TestNGMethod(/* m.getDeclaringClass(), */ m,
annotationFinder, xmlTest, null); /* @@@ */
vResult.put(key,tm);
}
}
} // for
// Now explore the superclass
cls = cls.getSuperclass();
} // while
...
...
查阅源码可以确认,TestNG在提取测试方法的时候查找的方法必须是public修饰且不能有返回值。当@Test注解放在类上时, 该类下默认使用public修饰且返回值为void的方法全部当作测试方法。那如果有多个@Test注解存在时它的执行顺序又是怎么样的呢?我们可以写一些简单的例子看下:
@Test
public class DefaultOrder {
// 有返回的方法会跳过
public String rpc() {
System.out.println(123);
return "123";
}
// 这是个私有的方法会跳过
private void testMethod() {
System.out.println("@Test");
}
public void a2() {
System.out.println("a2");
}
public void a1() {
System.out.println("a1");
}
public void b() {
System.out.println("b");
}
public void c() {
System.out.println("c");
}
public void d() {
System.out.println("d");
}
}
运行结果:
a1
a2
b
c
d
是不是很容就可以得出结论了?那么了解TestNG执行默认的执行顺序后我们是否可以按照自己的要求来指定顺序呢,那当然是可以的,我们在后续的内容中学习。
属性 | 作用 |
---|---|
alwaysRun | 如果取值为true,那么不论这个测试方法依赖的前置测试是否成功都会执行当前的方法。 |
dataProvider | 取值为测试方法的数据驱动方法的名称(@DataProvider中name参数的值)。 |
dataProviderClass | 取值为数据驱动方法所在的类名。如果未指定,将在当前测试方法的类或其基类之一上查找数据驱动方法。 需要配合dataProvider属性一起使用。 |
dependsOnGroups | 指定当前测试方法所依赖的组。 |
dependsOnMethods | 指定当前测试方法所依赖的方法。 |
description | 测试方法的说明,会在报告中显示。 |
enabled | 是否执行该类/方法上的测试。 |
expectedExceptions | 期望测试方法将引发的异常列表。如果未抛出异常或此列表中的异常,则该测试将标记为失败。 |
groups | 该类/方法所属的组。 |
invocationCount | 应该调用此方法的次数。 |
invocationTimeOut | 对于所有调用计数的累积时间,该测试应花费的最大毫秒数。如果未指定invocationCount,则将忽略此属性。 |
priority | 指定测试方法的优先级。值小的先执行。 |
successPercentage | 该方法预期成功的百分比。 |
timeOut | 当前测试方法的超时时间。 |
threadPoolSize | 指定方法的线程池的大小。该方法将从invocationCount指定的多个线程中调用。注意:如果未指定invocationCount,则忽略此属性。 |