TestNG牛刀小试

TestNG牛刀小试

本文提供了一个还算完整的TestNG使用示例,文中关于测试代码的部分,笔者只是用了testng一部分比较基础和常用的注解,其他更加详细和高级的用法还得参考TestNG官档或者是TestNG中文文档。关于该TestNG的使用示例,笔者大致将其划分为如下五个部分:

1、环境准备

首先肯定是要新建一个项目testng_demo,所有的测试case都放在该项目下。笔者使用的IDE是Eclipse,在开发测试case之前,需要先安装一个TestNG的插件:TestNG for Eclipse,至于安装方法,用过eclipse的人应该都清楚,比较简单方便,这里就不再赘述了。安装该插件的目的为了运行调试TestNG测试代码,也就是所谓的测试case,调试方法右键run as->TestNG Test。

然后是添加必要的依赖包,这一步可以用maven来配置,但是为了方便后边利用Ant启动测试,这里通过手动下载依赖包,放到项目根目录下的lib文件夹中(该文件夹是自建的,名字叫阿猫阿狗都行),然后将该lib目录添加到项目的classpath。需要下载的依赖包是:

生成测试报告还需要一个xsl样式表:testng-results.xsl,把它放到项目根目录下。

2、开发测试case

环境都准备好之后,接下来就可以利用TestNG框架开发测试case了,因为只是示例,笔者只创建了三个文件:

  • Calculator.java:测试目标类
  • BaseTest.java:所有测试类的基类
  • StandardTest.java:该文件中定义了多个测试类,用来演示TestNG中不同注解的用法
Calculator.javaBaseTest.javaStandardTest.java
package com.vdcoding.testngDemo.pojos;

public class Calculator {
	
	public int sum(int a, int b){
		return a + b;
	}
}
package com.vdcoding.testngDemo.tests;

import org.testng.annotations.BeforeMethod;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.DataProvider;

import com.vdcoding.testngDemo.pojos.Calculator;

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 BaseTest {
  Calculator calculator = new Calculator();
  
  @BeforeSuite
  public void beforeSuite() {
	  System.out.println("Before suite");
  }

  @AfterSuite
  public void afterSuite() {
	  System.out.println("After suite");
  }
  
  @BeforeTest
  public void beforeTest() {
	  System.out.println("Before test");
  }

  @AfterTest
  public void afterTest() {
	  System.out.println("After test");
  }
  
  @BeforeClass
  public void beforeClass() {
	  System.out.println("Before class");
  }

  @AfterClass
  public void afterClass() {
	  System.out.println("After class");
  }
	
  @BeforeMethod
  public void beforeMethod() {
	  System.out.println("Before method");
  }

  @AfterMethod
  public void afterMethod() {
	  System.out.println("After method");
  }
  
  @DataProvider(name="dp", parallel=true)
  public Object[][] dp() {
    return new Object[][] {
      new Object[] { 1, 3 },
      new Object[] { 2, 4 },
    };
  }
  
  @DataProvider(name="dp2")
  public Object[][] dp2(){
	  return new Object[][] {
	      new Object[] { 1, 3 },
	      new Object[] { 2, 4 },
	    };
  }

}
package com.vdcoding.testngDemo.tests;

import static org.testng.Assert.assertEquals;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Iterator;

import org.testng.annotations.DataProvider;
import org.testng.annotations.Ignore;
import org.testng.annotations.Test;

import com.vdcoding.testngDemo.pojos.Calculator;

/*
 * 利用注解@Test声明test case的方式(文中所出现的case表示被@Test注解的类或者方法):
 * 1、标注在方法上,该方法被标记为要执行的测试case
 * 2、标注在类上,则该类下的所有public方法都被标记为要执行的case,一般情况下,都应该使用将@Test标注在类上的方式。
 * Notes:
 * 使用了@Ignore或者@Test(enabled=false)的方法不会被执行,
 * 官方文档上说在方法上使用@Ignore注解等同于使用@Test(enabled=false),但实际测试@Ignore标注的方法还是会执行。
 * 
 */

public class StandardTest extends BaseTest{
  @Test(dataProvider = "dp", groups={"p0"})
  public void test(int a, int b) {
	  Calculator calculator = new Calculator();
	  assertEquals(a + b, calculator.sum(a, b));
  }
}

@Test(groups={"p1"})
class StandardTest2 extends BaseTest{
	
	public void test1(){
		System.out.println("hellodog");
	}
	
	@Ignore
	public void test2(){
		System.out.println("Ignored test!");
		
	}
	
	@Test(enabled=false)
	public void test3(){
		System.out.println("Disabled test");
	}
	
}

@Test(groups={"p2"})
class StandardTest3 extends BaseTest{
	/*
	 * depengsOnGroups=p1表示该用例的执行依赖于分组为p1的case,需要等p1的所有case都执行完了才会执行。
	 * 如果p1中有任意case执行失败了,则会跳过该case即StandardTest3.test1的执行。
	 * 如果属性alwaysRun=true,则不管p1的case是否执行成功,都会执行 StandardTest3.test1。
	 * 这种依赖其他方法的方式一般用来控制某些特殊case的执行顺序
	 */
	@Test(dataProvider="dp2", dependsOnGroups={"p1"}, alwaysRun=true)
	public void test1(int a, int b){
		assertEquals(a + b, calculator.sum(a, b));
	}
	/*
	 * dependsOnMethods={"test1"}表示test1执行成功后才会执行test2
	 */
	@Test(dependsOnMethods={"test1"})
	public void test2(){
		System.out.println("hello world");
	}
}

class MyDataProvider {
	/*
	 * 作为一个dataprovider是要有职业操守的:首先它职能作用在方法上,其次它只能返回如下两种数据结构:
	 * 1、返回一个元素类型为Object的二维数组,该二维数组长度指定了case的执行次数,也就是case会遍历该数组;
	 * 数组的元素Object[]则会按照顺序传给测试方法定义的形参,所以无论是数据类型还是个数,都必须与测试方法的形参匹配。
	 * 2、返回一个迭代器Iterator<Object[]>,这种方式与直接返回二维数组的唯一区别就是你懂得
	 */
	@DataProvider(name="mydp")
	public static Object[][] dp() {
		return new Object[][] {
			new Object[] { 1, 3 },
			new Object[] { 2, 4 },
		};
	}
	
	@DataProvider(name="iterdp")
	public static Iterator<Object[]> iterdp(){
		ArrayList<Object[]> list = new ArrayList<Object[]>();
		list.add(new Object[] { 1, 3 });
		list.add(new Object[] { 2, 4 });
		return list.listIterator();
	}
	
	/*
	 * 如果给dataprovider方法传入参数java.lang.reflect.Method,则该Method对应的是使用这个dp方法的测试方法
	 * 参考StandardTest4中的test3和test4
	 */
	@DataProvider(name="dprouter")
	public static Object[][] dpRouter(Method m) {
		if(m.getName().equals("test3")){
			return new Object[][] {new Object[] { 1, 3 }};
		}
		else if(m.getName().equals("test4")){
			return new Object[][] {new Object[] {2, 4}};
		}
		else{
			return new Object[][] {
				new Object[] { 1, 3 },
				new Object[] { 2, 4 },
			};
		}
		
	}
}

@Test(groups={"p3"})
class StandardTest4 extends BaseTest{
	/*
	 * 默认情况下,如果某个case声明了dataprovider则会在该case所在的当前类或其父类中查找。
	 * 如果想要引用其他类中定义的dataprovider方法,则该方法必须声明为static,如上边的MyDataProvider类所示,然后按照如下
	 * 方式引用即可
	 */
	@Test(dataProvider="mydp", dataProviderClass=MyDataProvider.class)
	public void test1(int a, int b){
		assertEquals(a + b, calculator.sum(a, b));
	}
	
	@Test(dataProvider="iterdp", dataProviderClass=MyDataProvider.class)
	public void test2(int a, int b){
		assertEquals(a + b, calculator.sum(a, b));
	}
	
	/*
	 * test3使用了dprouter,此时只会传入参数{ 1, 3 }
	 */
	@Test(dataProvider="dprouter", dataProviderClass=MyDataProvider.class)
	public void test3(int a, int b){
		assertEquals(a + b, calculator.sum(a, b));
	}
	
	/*
	 * test4使用了dprouter,此时只会传入参数{ 2, 4 }
	 */
	@Test(dataProvider="dprouter", dataProviderClass=MyDataProvider.class)
	public void test4(int a, int b){
		assertEquals(a + b, calculator.sum(a, b));
	}
	
}

3、创建testng.xml

测试case写完之后呢,现在来编写testng.xml(文件名字可以自定义,这里只是泛称),通过它来创建TestSuite,笔者称之为测试用例集。可以创建多个testng.xml,每一个文件就代表一个测试用例集,用来满足不同的测试需求和场景。笔者根据上边开发的测试case编写了两个测试用例集,放在项目根目录的test_suites文件夹下,这里只是为了演示多个测试用例集的情况,文件内容有重叠。

testng1.xmltestng2.xml
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" >
<!-- 一个xml文件中只能定义一个suite根元素,多个测试suite需要定义多个xml文件 -->
<suite name="Suite1" verbose="1" >
	<test name="Regression1">
		<!-- groups标签必须定义在packages或classes标签之前,否则编辑器会提示错误 -->
		<groups>
 			<!-- 利用define定义一个分组集合,包含了测试代码中定义的实际分组 -->
 			<define name="all">
 				<include name="p0"/>
 				<include name="p1"/>
 				<include name="p2"/>
 				<include name="p3"/>
 			</define>
 			<define name="p12">
 				<include name="p1"/>
 				<include name="p2"/>
 			</define>
 			<!-- 执行all分组集合和分组p1 -->
 			<run>
 				<include name="all"/>
 				<include name="p1"/>
 			</run>
 		</groups>
    	<packages>
			<!-- 该包下所有的testNG测试类都会执行 -->
      		<package name="com.vdcoding.testngDemo.tests" />
   		</packages>
   		
 	</test>
 	<test name="Regression2">
 		<classes>
 			<!-- 只执行声明的类中的所有测试方法 -->
 			<class name="com.vdcoding.testngDemo.tests.StandardTest"></class>
 		</classes>
 	</test>
 	<test name="notest"></test>
</suite>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" >
<!-- 一个xml文件中只能定义一个suite根元素,多个测试suite需要定义多个xml文件 -->
<suite name="Suite2" verbose="1" >
	<test name="Regression1">
		<!-- groups标签必须定义在packages或classes标签之前,否则编辑器会提示错误 -->
		<groups>
 			<!-- 利用define定义一个分组集合,包含了测试代码中定义的实际分组 -->
 			<define name="all">
 				<include name="p0"/>
 				<include name="p1"/>
 				<include name="p2"/>
 				<include name="p3"/>
 			</define>
 			<define name="p12">
 				<include name="p1"/>
 				<include name="p2"/>
 			</define>
 			<run>
 				<include name="p12"/>
 			</run>
 		</groups>
    	<packages>
			<!-- 该包下所有的testNG测试类都会执行 -->
      		<package name="com.vdcoding.testngDemo.tests" />
   		</packages>
   		
 	</test>
 	<test name="Regression2">
 		<classes>
 			<!-- 只执行声明的类中的所有测试方法 -->
 			<class name="com.vdcoding.testngDemo.tests.StandardTest"></class>
 		</classes>
 	</test>
</suite>

如果想要调试单个测试用例集,直接在该xml文件上右键run as->TestNG Test,与调试单条测试case的方法是一样的。

4、利用Ant运行测试

Eclipse是默认集成了Ant,所以这里直接编写build.xml,用来编译打包以及运行TestNG测试。该文件直接放到项目根目录下:

build.xml

然后直接在build.xml文件上右键run as->Ant Build,如果配置都正常的话就可以在控制台看到Ant构建成功的提示。

5、测试报告

利用Ant运行TestNG测试的话会默认输出测试报告到test-output目录中,不过该测试报告比较矬,所以在上一步的build.xml中添加了美化测试报告的任务

<target name="testoutput" depends="runtest" >
	<xslt in="${base_dir}/test-output/testng-results.xml" style="${base_dir}/testng-results.xsl"
	out="${beautify_result_dir}/${CURTIME}/index.html " >
		<!-- 指定testNgXslt.outputDir时,路径一定要使用绝对路径,因为expression默认会被当作字符串处理,否则会报IO Error无法找到指定路径,-->
		<param name= "testNgXslt.outputDir" expression="${beautify_result_dir}/${CURTIME}"/>
		<param name="testNgXslt.showRuntimeTotals" expression="true"/>
		<param name="testNgXslt.sortTestCaseLinks" expression="true" />
		<param name="testNgXslt.testDetailsFilter" expression="FAIL,SKIP,PASS,CONF,BY_CLASS" />
		<classpath refid= "dependent_jar" />
	</xslt>
</target>

该任务会利用testng-results.xsl样式表给test-output目录中的原生测试报告testng-results.xml做个整容,生成更加美观方便阅读的测试报告并输出到beautify_report目录中,每次运行都会以日期时间命名创建新的子目录来保存生成的测试报告。整容后的测试报告如下所示:

相比于原生的测试报告要好很多了,毕竟整容不能白做。没有比较就没有伤害,这里就不展示原生的测试报告了。

以上示例都是基于本地的操作,查看测试报告只需点开本地的html文件即可,如果是持续集成的话肯定是在服务器上进行构建的,相应的测试报告也在服务器上,查看时不是很方便,可以在服务器上起一个webserver,指向测试报告目录,这样就可以直接在浏览器中访问了,如下所示:

直接点击对应的链接就可以查看该次构建的测试报告了,笔者为了演示,这里是利用python -m http.server在本地启动了一个webserver,默认端口8000,http模块是python3中新增的,如果是python2的话则使用SimpleHTTPServer。

当然上面只是做个简单的演示,真实的场景中可以做的更加完美,完全取决于工作的需求。

示例代码Github下载地址:testng_demo

说点什么

avatar
  订阅  
提醒