测试套件
测试套件(Test Suite)是一种用于组织和管理测试的概念。测试套件允许你将多个测试类(Test Classes)以及它们的配置选项(如参数化、依赖关系等)组合在一起,并按照你的需求来执行它们。
测试类(Test Classes)
测试套件中的基本组成部分是测试类。这些类包含了需要测试的代码的测试方法。
在执行 测试方法
之前和之后,会执行与测试类相关的测试方法。例如下代码,就会在 helloWorld()
之前执行 setUp()
,并在之后执行 tearDown()
package com.mahe666;
import org.testng.annotations.*;
public class TestClass {
@BeforeClass
public void setUp(){
System.out.println("setUp!");
}
@Test
public void helloWorld(){
System.out.println("TestClass");
}
@AfterClass
public void tearDown(){
System.out.println("tearDown!");
}
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
<suite name="MySuite">
<test name="MyTest">
<classes>
<class name="com.mahe666.TestClass" />
</classes>
</test>
</suite>
测试类是一个Java类,它包含一个或多个测试方法。测试类用于组织和管理相关的测试方法,通常与被测试的类或组件对应。在测试类中,你可以使用各种测试框架(如JUnit、TestNG等)提供的注解来标识测试方法、设置测试环境和配置测试选项。
这就是一个简单的测试类的编写和运行过程。通过编写测试类,你可以确保你的代码按照预期进行工作,并且可以方便地进行回归测试以捕获潜在的bug。 TestNG提供了许多其他功能,如参数化测试、依赖测试、测试组等,你可以进一步学习以充分利用这个强大的测试框架。
测试方法(Test Methods)
测试类中的方法,用于测试被测代码的不同部分或功能。这些方法被 @Test
注解标记,以便TestNG能够识别并执行它们。
package com.mahe666;
import org.testng.annotations.*;
public class TestMethod {
@Test
public void testMethod1(){
System.out.println("TestMethod1");
}
@Test
public void testMethod2(){
System.out.println("TestMethod2");
}
@Test
public void testMethod3(){
System.out.println("TestMethod3");
}
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
<suite name="MySuite">
<test name="MyTest">
<classes>
<class name="com.mahe666.TestMethod">
<methods>
<include name="testMethod1"/>
<include name="testMethod2"/>
</methods>
</class>
</classes>
</test>
</suite>
测试方法是测试类中的方法,用于测试被测代码的特定行为或功能。每个测试方法通常对应于被测试类中的一个方法或一组相关的方法。测试方法应该包含测试用例,即一组输入和预期输出,以验证被测试方法的正确性。在测试方法中,你可以使用断言(assertions)来检查实际输出是否与预期输出一致。
测试类与测试方法的区别
- 作用范围:测试类用于组织和管理一组相关的测试方法,而测试方法用于具体测试被测代码的功能或行为。
- 代码结构:测试类是一个Java类,通常包含多个测试方法,而测试方法是测试类中的具体方法,用于执行测试逻辑。
- 注解使用:在测试类中,你可以使用各种测试框架提供的注解来配置测试环境和测试选项,而测试方法通常使用注解来标识它们是测试方法,并指定执行顺序等信息。
- 组织方式:测试类是测试代码的最基本组织单位,它将相关的测试方法组织在一起,方便管理和维护。而测试方法是具体的测试用例,它们组成了测试类的核心内容,用于验证被测代码的正确性。
配置方法(Configuration Methods)
TestNG 允许在测试执行前后执行一些配置操作。
这些操作可以通过 @BeforeSuite
、@AfterSuite
、@BeforeTest
、@AfterTest
、@BeforeClass
、@AfterClass
、@BeforeMethod
、@AfterMethod
等注解来定义。
@BeforeSuite
和@AfterSuite
这两个方法分别在整个测试套件执行前后执行,通常用于设置全局配置和清理操作。
import org.testng.annotations.*;
public class TestConfigurationMethod {
@BeforeSuite
public void setUpSuite() {
// 设置全局配置
}
@AfterSuite
public void tearDownSuite() {
// 清理操作
}
}
@BeforeTest
和@AfterTest
这两个方法分别在每个 <test>
标签内的测试执行前后执行,通常用于设置测试前的准备工作和清理操作。
import org.testng.annotations.*;
public class TestConfigurationMethod {
@BeforeTest
public void setUpTest() {
// 测试前准备工作
}
@AfterTest
public void tearDownTest() {
// 测试后清理操作
}
}
@BeforeClass
和@AfterClass
这两个方法分别在每个测试类执行前后执行,通常用于设置测试类的初始化和清理操作。
import org.testng.annotations.*;
public class TestConfigurationMethod {
@BeforeClass
public void setUpClass() {
// 测试类初始化操作
}
@AfterClass
public void tearDownClass() {
// 测试类清理操作
}
}
@BeforeMethod
和@AfterMethod
这两个方法分别在每个测试方法执行前后执行,通常用于设置测试方法的前置条件和清理操作。
import org.testng.annotations.*;
public class TestConfigurationMethod {
@BeforeMethod
public void setUpMethod() {
// 测试方法前置条件设置
}
@AfterMethod
public void tearDownMethod() {
// 测试方法清理操作
}
}
配置方法的执行顺序是按照 TestNG 注解的定义顺序执行的。通过合理使用配置方法,你可以确保测试执行前后的环境准备和清理工作得到正确地执行,从而提高测试的稳定性和可靠性。
测试依赖(Dependencies)
您可以使用依赖注解来定义测试方法之间的依赖关系,以确保测试方法按照正确的顺序执行。
可以看出来,实际执行顺序哥Java代码中的测试方法顺序不一样
package com.mahe666;
import org.testng.annotations.Test;
public class TestDependency {
@Test(dependsOnMethods = {"testHelloWorld"})
public void testMain(){
System.out.println("main");
}
@Test
public void testHello(){
System.out.println("hello");
}
@Test(dependsOnMethods = {"testHello"})
public void testHelloWorld(){
System.out.println("helloWorld");
}
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
<suite name="MySuite">
<test name="MyTest">
<classes>
<class name="com.mahe666.TestDependency" >
<methods>
<include name="testHello" />
<include name="testHelloWorld" />
<include name="testMain" />
</methods>
</class>
</classes>
</test>
</suite>
通过测试依赖,你可以更好地控制测试方法的执行顺序,并确保测试的准确性和可靠性。
注意事项
- 测试方法之间的依赖关系可能会增加测试之间的耦合性。因此,你需要谨慎地设计测试依赖,避免产生不必要的依赖关系。
- 测试依赖关系应该是明确的和可靠的。确保依赖方法的执行结果对于依赖于它的方法是可靠的。
测试组(Test Groups)
通过分组测试方法,可以选择性地运行一组测试。这对于在不同的测试场景中选择性地运行测试非常有用。
想象一下你正在建一座房子。你会把一堆砖块分成几组,每组都有不同的用途。比如,有些砖块用来建墙,有些用来建地板,还有些用来建屋顶。测试组就像是把这些砖块分类一样,把一些相关的测试放在一起。比如,你可能会有一个测试组来测试登录功能,另一个测试组来测试注册功能,以此类推。
这样做有几个好处。首先,它让你的测试更有组织性,更容易管理。如果你需要修改或者添加新的测试,你只需要去对应的测试组里找到它们。其次,测试组可以帮助你更有效地运行测试。你可以选择只运行某个测试组,而不必一次性运行所有的测试,这样可以节省时间。最后,测试组还可以帮助你更好地理解你的测试覆盖范围。通过查看每个测试组包含了哪些测试,你可以更清楚地知道你的测试是否覆盖了所有需要测试的功能。
总之,测试组就像是把相关的测试放在一起,让你更方便地管理和运行它们。
package com.mahe666;
import org.testng.annotations.Test;
public class TestGroup {
@Test(groups = {"group1"})
public void testMethod1() {
System.out.println("Group 1 - Test method 1");
}
@Test(groups = {"group2"})
public void testMethod2() {
System.out.println("Group 2 - Test method 2");
}
@Test(groups = {"group1", "group2"})
public void testMethod3() {
System.out.println("Group 1, Group 2 - Test method 3");
}
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
<suite name="MySuite">
<test name="MyTest">
<groups>
<run>
<!--包含哪些组使用 include-->
<include name="group1" />
<!--排除哪些组使用 exclude-->
<!-- <exclude name="group2" />-->
</run>
</groups>
<classes>
<class name="com.mahe666.TestGroup" />
</classes>
</test>
</suite>
如上述代码所示,当 <exclude name="group2" />
被注释掉了之后,会执行所有包含了组名为 group1
的测试方法。但是当把注释去掉了,就会把同时包含组名为 group1
和 group2
的测试方法排除掉,不再执行。
执行关系
执行优先级: <include>
标签的执行优先级高于 <exclude>
标签。也就是说,如果一个测试组同时出现在 <include>
和 <exclude>
标签中,它将会被包含在执行中。
逻辑关系: <include>
和 <exclude>
标签内部的测试组之间是逻辑与(AND)关系。也就是说,只有同时满足 <include>
和 <exclude>
标签内所有条件的测试组才会被执行。
注意事项
- 测试组应该是明确的和有意义的。确保为每个测试方法指定正确的组,并合理地组织测试方法。
- 避免过度依赖测试组,以避免增加测试之间的耦合性。
测试监听器(Test Listeners)
测试监听器(Test Listeners)是 TestNG 中的一个重要功能,它允许你在测试执行的不同阶段插入自定义的行为,比如在测试开始前或结束后执行一些操作,或者在测试方法执行成功或失败时执行一些操作。测试监听器通常被用于日志记录、报告生成、异常处理等方面。
package com.mahe666;
import org.testng.ITestContext;
import org.testng.ITestListener;
import org.testng.ITestResult;
public class TestListener implements ITestListener {
@Override
public void onStart(ITestContext context) {
// 在测试开始前执行的操作
System.out.println("Test suite started: " + context.getName());
}
@Override
public void onFinish(ITestContext context) {
// 在测试结束后执行的操作
System.out.println("Test suite finished: " + context.getName());
}
@Override
public void onTestStart(ITestResult result) {
// 在每个测试方法开始执行前执行的操作
System.out.println("Test started: " + result.getMethod().getMethodName());
}
@Override
public void onTestSuccess(ITestResult result) {
// 在测试方法执行成功后执行的操作
System.out.println("Test passed: " + result.getMethod().getMethodName());
}
@Override
public void onTestFailure(ITestResult result) {
// 在测试方法执行失败后执行的操作
System.out.println("Test failed: " + result.getMethod().getMethodName());
}
}
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
<suite name="TestSuite" verbose="1">
<listeners>
<listener class-name="com.mahe666.TestListener"/>
</listeners>
<!-- 测试配置 -->
</suite>
这样配置之后,TestListener 中定义的监听器将会在测试执行过程中起作用,你可以在监听器中实现自定义的逻辑来满足你的测试需求。
警告
上述代码只是单纯的如何编写并配置监听器,并不是实际的测试用例代码
注意事项
- 在实现自定义的监听器时,确保实现了正确的接口方法,并处理了相应的事件。
- 监听器中的操作可能会对测试执行性能产生影响,因此要谨慎设计监听器中的逻辑。
- 了解每个监听器接口的作用和生命周期,以便更好地控制测试过程中的行为。
套件级别设置(Suite-level Settings)
套件级别设置(Suite-level Settings)是在 TestNG 中用于控制整个测试套件行为的配置选项。这些设置适用于整个测试套件中的所有测试,并可以在 testng.xml 文件中进行指定
并行执行设置
<suite name="TestSuite" parallel="tests" thread-count="2">
<!-- 测试配置 -->
</suite>
parallel
属性用于指定测试方法的并行执行方式,可以设置为tests
、classes
或methods
。例如配置的是tests
,则每个测试套件<suite>
中的测试集<test>
都会并行执行thread-count
属性用于指定并行执行时的线程数量。
在配置了并行执行后,TestNG 将会根据指定的方式和级别来并行执行测试方法,从而提高测试效率。注意,在进行并行测试时,要确保测试方法之间没有依赖性,以避免出现不确定的测试结果。
注意
当你配置 parallel="methods"
和 parallel="classes"
时,使用 thread-count
属性不会生效。因为在方法级别的并行执行中,每个测试方法都在自己的线程中执行,而不是在线程池中。因此,thread-count
属性只在配置了 parallel="tests"
或 parallel="threads"
时才会生效,用于指定线程池中的线程数量。
如果你想控制方法级别的并行执行的线程数量,可以使用 Java 自带的线程池和并发机制,在测试方法内部手动创建线程池并控制线程数量。但请注意,在 TestNG 的测试方法中使用多线程可能会引入一些复杂性和不确定性,需要小心处理。
监听器设置
<suite name="TestSuite">
<listeners>
<listener class-name="com.example.MyTestListener"/>
</listeners>
</suite>
注意
生成测试报告的设置,也同样是以监听器的方式实现的
参数设置
<suite name="TestSuite">
<parameter name="param" value="123456"/>
</suite>
这个没啥好说的
测试超时设置
<suite name="TestSuite">
<test name="Test" time-out="5000">
<!-- 测试配置 -->
</test>
</suite>
或者使用如下的方式
import org.testng.annotations.Test;
public class MyTestClass {
@Test(timeOut = 5000) // 设置超时时间为 5 秒
public void testMethod() {
// 在这里执行测试方法的逻辑
}
}
设置测试执行的最大超时时间为 5000 毫秒
禁用测试设置
<suite name="TestSuite">
<test name="Test">
<classes>
<class name="MyTestClass" enabled="false">
<!-- 类级别配置 -->
</class>
</classes>
</test>
</suite>
或者使用如下的方式
import org.testng.annotations.Test;
public class MyTestClass {
@Test(enabled=false)
public void testMethod() {
// 在这里执行测试方法的逻辑
}
}
禁用了名为 MyTestClass
的测试类
用例排序设置
<suite name="TestSuite">
<test name="Test" preserve-order="true">
<!-- 测试配置 -->
</test>
</suite>
保留测试方法的执行顺序
测试结果信息设置
<suite name="TestSuite">
<test name="Test" verbose="2">
<!-- 测试配置 -->
</test>
</suite>
在 TestNG 中,verbose
属性用于控制测试运行期间的详细程度。它可以设置为不同的值,以指定不同的详细级别。常见的值包括:
0
:仅显示最关键的信息,例如总体的测试结果。1
:显示更多的信息,例如每个测试方法的执行结果。2
:显示最详细的信息,例如每个测试方法的执行时间、堆栈跟踪等。
因此,当你将 verbose
属性设置为 2
时,TestNG 将会在测试运行期间以最详细的方式显示信息,包括每个测试方法的执行时间、堆栈跟踪等。这对于诊断和调试测试用例非常有用,因为它提供了更多的执行细节。
失败重试设置
package com.mahe666;
import org.testng.IRetryAnalyzer;
import org.testng.ITestResult;
public class TestRetryAnalyzer implements IRetryAnalyzer {
private int count = 0;
private static final int MAX_RETRY_COUNT = 3; // 最大重试次数
@Override
public boolean retry(ITestResult result) {
if (!result.isSuccess() && count < MAX_RETRY_COUNT) {
count++;
return true; // 如果测试方法失败且重试次数小于最大重试次数,则返回 true,表示应该重试
}
return false; // 否则返回 false,表示不应该重试
}
}
<suite name="TestSuite">
<listeners>
<listener class-name="com.mahe666.TestRetryAnalyzer" />
</listeners>
</suite>
在整个测试套件中的所有测试方法失败时,TestNG 将会调用这个自定义的 TestRetryAnalyzer
进行重试判断
确保在 <suite>
或 <test>
标签内添加了 <listeners>
子标签,并在其中指定了自定义的 TestRetryAnalyzer
类,这样才能正确地注册并使用自定义的失败重试监听器。
测试报告(Test Reports)
TestNG 生成详细的测试报告,其中包含有关测试运行的各种信息,如测试通过率、失败的测试用例、测试时间等。您可以使用 TestNG 默认的报告生成器,也可以集成其他报告插件,如ExtentReports、Allure 等。
默认的HTML生成配置如下
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" >
<suite name="MyTestSuite">
<listeners>
<listener class-name="org.testng.reporters.TestHTMLReporter"/>
</listeners>
<test name="MyTest">
<classes>
<class name="MyTestClass"/>
</classes>
</test>
</suite>
也可以自定义测试报告生成器
package com.mahe666;
import org.testng.IReporter;
import org.testng.ISuite;
import org.testng.ISuiteResult;
import org.testng.ITestContext;
import org.testng.xml.XmlSuite;
import java.util.List;
import java.util.Map;
public class TestReporter implements IReporter {
@Override
public void generateReport(List<XmlSuite> xmlSuites, List<ISuite> suites, String outputDirectory) {
// 遍历所有测试套件
for (ISuite suite : suites) {
// 获取测试套件的名称
String suiteName = suite.getName();
// 获取该测试套件的所有测试结果
Map<String, ISuiteResult> suiteResults = suite.getResults();
for (ISuiteResult suiteResult : suiteResults.values()) {
ITestContext testContext = suiteResult.getTestContext();
// 获取测试上下文的名称
String testName = testContext.getName();
// 获取测试结果
System.out.println("Test Name: " + testName);
System.out.println("Passed tests: " + testContext.getPassedTests().getAllResults().size());
System.out.println("Failed tests: " + testContext.getFailedTests().getAllResults().size());
System.out.println("Skipped tests: " + testContext.getSkippedTests().getAllResults().size());
// 进一步处理测试结果并生成报告,例如输出到文件或其他格式
// 这里只是简单打印结果到控制台
}
}
}
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
<suite name="MySuite">
<listeners>
<listener class-name="com.mahe666.TestReporter" />
</listeners>
<test name="MyTest">
<classes>
<class name="com.mahe666.TestHelloWorld" />
</classes>
</test>
</suite>
依赖注入(Dependency Injection)
TestNG 支持使用 Guice 或 Spring 等依赖注入框架,以便更容易地管理测试类之间的依赖关系。
测试数据管理(Test Data Management)
TestNG 允许您灵活地管理测试数据,可以从外部文件加载数据,也可以使用内置的数据提供者来动态生成数据。
外部文件加载数据 @DataProvider
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
public class DataProviderTest {
@Test(dataProvider = "getData")
public void run(String c1, String c2, String c3) {
System.out.println(c1 + c2 + " ===> " + c3);
}
@DataProvider
public String[][] getData() {
return new String[][]{{"Hello", "World", "hhh"}, {"Mahe", "666", "6翻了"}, {"Mahe", "888", "我想暴富"}};
}
}
从文件中获取Data
package com.mahe666.app;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
public class DataProviderTest {
@Test(dataProvider = "getData")
public void run(String c1, String c2, String c3) {
System.out.println(c1 + c2 + " ===> " + c3);
}
@DataProvider
public String[][] getData() throws IOException {
List<String[]> rows = new ArrayList<>();
// 把文件放在resources文件夹下 的 static文件夹下
File file = new File(this.getClass().getClassLoader().getResource("static/data.txt").getFile());
FileReader reader = new FileReader(file);
BufferedReader buffer = new BufferedReader(reader);
String row;
while ((row = buffer.readLine()) != null){
String columns[] = row.split("\t");
rows.add(columns);
}
reader.close();
String[][] data = new String[rows.size()][];
for (int i = 0; i < rows.size(); i++) {
data[i] = rows.get(i);
}
return data;
}
}
附件
调用其他类里的 DataProvider
示例如下
import org.testng.annotations.Test;
public class DataProviderTest {
@Test(dataProvider = "获取数据", dataProviderClass = Param.class)
public void run(String c1, String c2, String c3) {
System.out.println(c1 + c2 + " ===> " + c3);
}
}
import org.testng.annotations.DataProvider;
public class Param {
//注解加上name之后,就只能通过name实现调用了
@DataProvider(name = "获取数据")
public String[][] getParam() {
return new String[][]{{"Hello", "World", "hhh"}, {"Mahe", "666", "6翻了"}, {"Mahe", "888", "我想暴富"}};
}
}
内置的数据提供者 @Parameters 和 @Factory
Parameters
TestNG 支持 测试参数化
,允许您使用不同的输入数据运行相同的测试方法。这可以通过 @Parameters
注解实现。
可以使用 @Parameters
注解从testng.xml文件中获取参数,并将其提供给测试方法。
package com.mahe666;
import org.testng.annotations.*;
public class TestParameterization {
@Test
@Parameters({"num1", "num2"})
public void testAddition(int num1, int num2) {
int result = num1 + num2;
System.out.println("Addition result: " + result);
}
@Test
@Parameters({"num1", "num2"})
public void testSubtraction(int num1, int num2) {
int result = num1 - num2;
System.out.println("Subtraction result: " + result);
}
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
<suite name="MySuite">
<test name="MyTest">
<parameter name="num1" value="6"/>
<parameter name="num2" value="4"/>
<classes>
<class name="com.mahe666.TestParameterization" />
</classes>
</test>
</suite>
@Factory
import org.testng.annotations.Factory;
import org.testng.annotations.Test;
public class FactoryExample {
private int param;
// 构造函数用于接收参数
public FactoryExample(int param) {
this.param = param;
}
@Test
public void testMethod() {
System.out.println("Test method with parameter: " + param);
}
// 静态方法用于返回 Factory 实例
@Factory
public static Object[] createInstances() {
Object[] result = new Object[3]; // 创建 3 个测试实例
for (int i = 0; i < 3; i++) {
result[i] = new FactoryExample(i);
}
return result;
}
}
在上面的示例中,定义了一个测试类 FactoryExample
,该类包含一个带参数的构造函数和一个测试方法 testMethod
。然后使用 @Factory
注解标记了一个静态方法 createInstances()
,该方法返回了一个包含多个测试类实例的数组。
当 TestNG 运行测试时,它会自动调用 createInstances()
方法,生成指定数量的测试类实例。每个实例都使用不同的参数值初始化,然后在测试过程中执行相同的测试方法。这样,我们就可以动态地创建多个测试实例,从而更灵活地管理测试组织和执行。