写在前边
用单元测试Junit完全可以满足日常开发自测,为什么还要学习TestNG,都影响了我的开发进度!
最近技术部老大突然宣布:全体开发人员必须熟练掌握自动化测试框架TestNG,就有了上边同事们的抱怨,是的,开始我也在抱怨,因为并不知道它是个什么东东,但从开始接触到慢慢编写测试用例,应用到项目后,我发现它真的超实用。
我们来一起看看它比Junit好在哪?
一、TestNG初识
TestNG[后面都简称为TG]是一款为了大量测试(比如测试时多接口数据依赖)需要,所诞生的一款测试框架,从简单的单元测试再到集成测试甚至是框架级别的测试,都可以覆盖到,因此是一款非常强大的测试框架!
1.编写步骤
常规的TG的测试案例有三个步骤
-
编写业务测试代码时,插入TG提供的注解
-
将要测试的类、group等进行编排,写入到testng.xml或build.xml
-
运行单个TG测试案例【idea、eclipse、ant、cmd等都可以运行】
-
运行整个项目的test-ng测试案例【如果是多模块项目,则是进入到对应的模块目录运行命令或者配置】
-
xml
<plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <configuration> <skip>false</skip> <testFailureIgnore>false</testFailureIgnore> <suiteXmlFiles> <file>${project.basedir}/src/test/OrderTest.xml</file> </suiteXmlFiles> </configuration> </plugin>
-
MVN命令
此处的命令会优先于
pom.xml
中的配置,相对于java命令更优,因为mvn处理打包的一环,方便监控mvn clean package/install -DskipTests mvn clean package/install -Dmaven.test.skip=true #或者直接执行 mvn test
-
2.相关注解
注解 | 作用 |
---|---|
@BeforeSuite | 被注解的方法,将在整个测试套件之前运行 |
@AfterSuite | 被注解的方法,将在整个测试套件之后运行 |
@BeforeTest | 被注解的方法,将在测试套件内所有用例执行之前运行 |
@AfterTest | 被注解的方法,将在测试套件内所有用例执行之后运行 |
@BeforeGroups | 被注解的方法,将在指定组内任意用例执行之前运行 |
@AfterGroups | 被注解的方法,将在指定组内任意用例执行之后运行 |
@BeforeClass | 被注解的方法,将在此方法对应类中的任意其他的,被标注为@Test 的方法执行前运行 |
@AfterClass | 被注解的方法,将在此方法对应类中的任意其他的,被标注为@Test 的方法执行后运行 |
@BeforeMethod | 被注解的方法,将在此方法对应类中的任意其他的,被标注为@Test的方法执行前运行 |
@AfterMethod | 被注解的方法,将在此方法对应类中的任意其他的,被标注为@Test的方法执行后运行 |
@DataProvider | 被注解的方法,强制返回一个 二维数组Object[ ][ ]作为另外一个@Test方法的数据工厂 |
@Factory | 被注解的方法,作为对象工厂,强制返回一个对象数组 Object[ ] |
@Listeners | 定义一个测试类的监听器 |
@Parameters | 定义一组参数,在方法运行期间向方法传递参数的值,参数的值在testng.xml中定义 |
@Test | 标记方法为测试方法,如果标记的是类,则此类中所有的public方法都为测试方法 |
备注:相关对应的属性配置值,点进去对应类中查询即可,不在一一赘述
3.名词解释
suite
由xml文件表示,包含一个或者多个测试案例,使用标签包裹
一般来讲,一个xml的<suite>
对应一个java类,除非特殊情况,在java中需要特别指定<suite>
否则xml对应java类的所有@Test注解属性suiteName默认都是xml中定义的<suite name='xxx'>
test
由标签表示,包含一个或者多个TestNG的类
1.Check-in tests 登记类测试
这些测试类需要在提交新代码前运行,保证基本功能不会被破坏
2.Functional tests 功能类测试
这些测试应该覆盖软件的所有功能,并且每天至少运行一次,即使有些情况下你不想运行它
check-in test是Functional tests的子集
基础示例
public class Test1 {
@Test(groups = { "functest", "checkintest" })
public void testMethod1() {
}
@Test(groups = {"functest", "checkintest"} )
public void testMethod2() {
}
@Test(groups = { "functest" })
public void testMethod3() {
}
}
<test name="Test1">
<groups>
<run>
<include name="functest"/>
<!-- <include name="checkintest"/> -->
</run>
</groups>
<classes>
<class name="example1.Test1"/>
</classes>
</test>
运行testng.xml文件结果:
所有@Test注解中,在xml中定义的1、2、3方法都会被运行,如果换成check-intest,则只运行方法1、2
总结:
1.xml文件定义测试案例的运行策略
2.所谓的测试案例类别,只是概念上的定义
--- 登记类的测试案例,如果不在xml编排中没有涉及,它不一定运行
--- 功能性测试类,是我们在xml编排中,一定会运行的测试案例
扩展示例
1.正则匹配
@Test
public class Test1 {
@Test(groups = { "windows.checkintest" })
public void testWindowsOnly() {
}
@Test(groups = {"linux.checkintest"} )
public void testLinuxOnly() {
}
@Test(groups = { "windows.functest" )
public void testWindowsToo() {
}
}
<test name="Test1">
<groups>
<run>
<include name="windows.*"/>
</run>
</groups>
<classes>
<class name="example1.Test1"/>
</classes>
</test>
大家发挥一下脑洞,应该可以猜到运行结果【不要被windows的命名所引诱】
2.正则排除
【官方原话,不建议使用此类写法】
如果您开始重构Java代码(标记中使用的正则表达式可能与您的方法不再匹配)
这会使您的测试框架很可能崩溃
package org.vk.test.springtest_testng;
import org.testng.annotations.Test;
public class Test1 {
@Test(groups = {"functest", "checkintest"})
public void testMethod1() {
System.out.println(1);
}
@Test(groups = {"functest", "checkintest"})
public void testMethod2() {
System.out.println(2);
}
@Test(groups = {"functest"})
public void testMethod3() {
System.out.println(3);
}
}
<suite name="Suite" parallel="classes" thread-count="1">
<test name="Test1">
<groups>
<run>
<include name="functest"/>
</run>
</groups>
<classes>
<class name="org.vk.test.springtest_testng.Test1">
<methods>
<include name="testMethod*"></include>
<exclude name="testMethod3"></exclude>
</methods>
</class>
</classes>
</test>
</suite>
运行结果:testMethod3不会执行
总结:suit配置,从上而下,其实是对编排规则的一个层层过滤
对应group配置,显然所有方法都会执行,但是到了class配置时,对其再次配置,过滤了方法3
test class
java类,包含至少一个TG的注解,由表示
test method
java方法,含有@Test注解,默认情况下,test方法的返回值都会忽略,除非声明需要返回
<suite allow-return-values="true">
<!--或者-->
<test allow-return-values="true">
test group
测试方法组,不仅可以定义方法属于哪个group,还可以设置group包含哪些子group,TG会自动调用
可以在testng.xml的<test>or<suite>
中定义
如果在中指定组“a”,在中指定组“b”,则“a”和“b”都将包括在内
示例1:基础
@Test(groups = {"checkintest", "broken"} )
public void testMethod2() {
}
<test name="Simple example">
<groups>
<run>
<include name="checkintest"/>
<exclude name="broken"/>
</run>
</groups>
<classes>
<class name="example1.Test1"/>
</classes>
</test>
运行结果:什么都没有
总结:xml配置,决定最终结果,无论出现什么反思维的配置,理论上什么结果就是最终结果
【官方提示】
达到禁用效果,也可以通过使用@Test和@Before/After注释上的“enabled”属性单独禁用测试。
示例2:组合
@Test(groups = { "checkin-test" })
public class All {
@Test(groups = { "func-test" )
public void method1() { ... }
public void method2() { ... }
}
结果:method1属于checkin-test和func-test两个组,method2仅属于checkin-test组
groups of groups
group里面含有子group,称为 MetaGroups
,我自己称为元组
functest和checkintest在名词解释的test小节中有提到,此处我们将其funtest再细化分windows、linux组
新增all组,包含两个大组
<suite name="Suite" parallel="classes" thread-count="1">
<test name="Test1">
<groups>
<define name="functest">
<include name="windows"/>
<include name="linux"/>
</define>
<define name="all">
<include name="functest"/>
<include name="checkintest"/>
</define>
<run>
<include name="all"/>
</run>
</groups>
<classes>
<class name="example1.Test1"/>
</classes>
</test>
</suite>
结果:所有方法都会运行
总结:可以对多个测试进行组合编排,形成group表示一个大的功能
testng.xml
testng.xml的每个section部分,都可以在ant、命令行对应的文档中找到【另外2种调用TG的方式】
个人理解:
一般来讲,直接在idea、eclipse中运行@Test类也可以,但为什么我们需要testng.xml?
原因:如果需要对java类、方法的测试案例进行编排,不使用xml进行编排,仅仅提供xml文件以外的方式,很难做到高度灵活的业务逻辑测试!
parameters
测试案例的参数,测试方法的入参配置,执行方法时用到
测试案例的参数
1.@Parameters
1.1 xml注入
@Parameters({ "first-name" })
@Test
public void testSingleString(String firstName) {
System.out.println("Invoked testString " + firstName);
assert "Cedric".equals(firstName);
}
<suite name="My suite">
<parameter name="first-name" value="Cedric"/>
<test name="Simple example">
<-- ... -->
</suite>
XML参数映射到Java参数的顺序与注释中的顺序相同,如果数量不匹配,TestNG将发出一个错误。
参数的作用域:
在testng.xml中,可以在标记下或下声明它们。
如果两个参数具有相同的名称,则在中定义的参数具有优先权。如果您需要指定一个适用于所有测试的参数,并仅对某些测试重写其值,则这很方便。
1.2 @Optional注入
@Parameters("hello")
@Test(groups = {"functest"})
public void testMethod4(@Optional("hello") String hello) {
System.out.println(hello);
}
【官方说明】
@Parameters注解,同样适用于
@Before/After
and@Factory
此类的注解!结果:输出hello字符串
总结:以上的方式,适合简单参数配置,不适合做复杂对象注入
2.@DataProvider
这种方式是为了弥补第一种方式而衍生的,如果参数构建比较复杂,复杂对象无法在xml或者利用@Optional注解
构建时,就需要这种方式了
示例:同一个类
//This method will provide data to any test method that declares that its Data Provider
//is named "test1"
@DataProvider(name = "test1",parallel = true)
public Object[][] createData1() {
return new Object[][] {
{ "Cedric", new Integer(36) },
{ "Anne", new Integer(37)},
};
}
//This test method declares that its data should be supplied by the Data Provider
//named "test1"
@Test(dataProvider = "test1")
public void verifyData1(String n1, Integer n2) {
System.out.println(n1 + " " + n2);
}
输出结果:
Cedric 36 Anne 37
备注:并发线程数设置可以在xml的<suite data-provider-thread-count="20">
中调整,默认配置10个线程
如果要在不同的线程池中运行一些特定的数据提供程序,则需要从不同的XML文件运行它们。
示例:跨类
public class StaticProvider {
@DataProvider(name = "create")
public static Object[][] createData() {
return new Object[][] {
new Object[] { new Integer(42) }
};
}
}
public class MyTest {
@Test(dataProvider = "create", dataProviderClass = StaticProvider.class)
public void test(Integer n) {
// ...
}
}
示例:其他类
@DataProvider(name = "test1")
public MyCustomData[] createData() {
return new MyCustomData[]{ new MyCustomData() };
}
@DataProvider(name = "test1")
public Iterator<MyCustomData> createData() {
return Arrays.asList(new MyCustomData()).iterator();
}
@DataProvider(name = "test1")
public Iterator<Stream> createData() {
return Arrays.asList(Stream.of("a", "b", "c")).iterator();
}
参数返回值说明
-
Object[] []
数组第一个维度的大小是调用测试方法的次数
数组第二个维度的大小包含必须与测试方法的参数类型兼容的对象数组
-
Iterator<Object[ ]>
和第一种返回类型不同,这种方式允许你对返回值做懒初始化
唯一的限制是在迭代器的情况下,它的参数类型不能被显式地参数化
如果有很多参数集要传递给方法,并且不想预先创建所有参数集,那么这一点特别有用
下面这几个例子都有一个特性: can't be explicitly parametrized
@DataProvider(name = "test1")
不允许显示的初始化,有使用经验的人可以私聊!目前我这边直接把代码粘上去,是会报错的。
factory
工厂还可以与数据提供程序一起使用,可以通过将@Factory注释放在常规方法或构造函数上来利用此功能
使用@Factory可动态地创建测试,一般用来创建一个测试类的多个实例,每个实例中的所有测试用例都会被执行,@Factory构造实例的方法必须返回Object[]。
下一个小节的dependencyes,就有对其应用,此处不做过多的说明了就,官网示例和其相差不大。
dependencyes
某些情况,我们需要对执行顺序做编排,TG提供了2种方式:
-
xml
<test name="My suite"> <groups> <dependencies> <group name="c" depends-on="a b" /> <group name="z" depends-on="c" /> </dependencies> </groups> </test>
-
注解
在
@Test
注解上设置依赖的属性:dependsOnMethods
或者dependsOnGroups
硬性依赖
依赖的所有方法都必须已运行并成功才能运行。
如果依赖项中至少发生一个错误,则不会在报表中调用并标记为跳过
默认情况下alwaysRun=false
@Test(groups = { "init" })
public void serverStartedOk() {}
@Test(groups = { "init" })
public void initEnvironment() {}
@Test(dependsOnGroups = { "init.*" })
public void method1() {}
如果依赖的方法失败,并且对它有硬依赖关系,则依赖它的方法不会标记为失败,而是标记为跳过。跳过的方法将在最终报告中以同样的方式报告(在HTML中,颜色既不是红色也不是绿色),这一点很重要,因为跳过的方法不一定是失败的。
高级用法参照【 Dependent test methods « Otaku – Cedric's blog 】
软性依赖
即使有些方法失败了,你也会一直在追求你所依赖的方法。
当您只想确保您的测试方法以特定的顺序运行,但它们的成功并不真正依赖于其他方法的成功时,这非常有用。
通过在@Test注释中添加alwaysRun=true
获得软依赖性。
group-by-instances
测试案例按照实例进行分组运行
通常的dependsOnGroups依赖注解,只能实现以下的模式:
a(1)
a(2)
b(2)
b(2)
但是也有一种情况, 假设我们要实现多组用户一组操作行为:登录、登出
signIn("us")
signOut("us")
signIn("uk")
signOut("uk")
此时我们需要使用@Factory注解,配合group-by-instance来实现
Test1.java
public class Test1 {
private String countryName;
public Test1(String countryName) {
this.countryName = countryName;
}
@Test
public void signIn() {
System.out.println(countryName + " signIn");
}
@Test(dependsOnMethods = "signIn")
public void signOut() {
System.out.println(countryName + " signOut");
}
}
Test.xml
<suite name="Suite">
<test name="Test1" >
<classes>
<class name="org.vk.test.springtest_testng.Test1"></class>
</classes>
</test>
</suite>
TestFactory.java
public class TestFactory {
@Factory(dataProvider = "init")
public Object[] test(int nums) {
Object[] object = new Object[nums];
List<String> ctrys = Arrays.asList("US", "UK", "HK");
for (int i = 0; i < nums;