1. 介绍
1.1 什么是 TestNG?
TestNG(Test Next Generation)是一个用于测试Java程序的测试框架。主要用于自动化测试,包括单元测试、集成测试、端到端测试等。虽然它被广泛用于白盒测试(例如单元测试),但并不局限于白盒测试。它支持并发、参数化测试、测试组织和测试报告生成。通过 TestNG 可以更灵活、高效地进行测试,并获得详尽的测试结果报告。
本文主要是探索使用 TestNG 框架进行白盒安全测试。
1.2 Java 版本要求
- TestNG <= v7.5:JDK 8。
- TestNG >= v7.6.0:JDK 11 或更高版本。
1.3 TestNG 编写和执行流程
使用 TestNG 编写和执行测试的基本流程如下:
- 导入依赖: 在项目中使用 Maven 或其他构建工具,导入 TestNG 的依赖,或下载 TestNG JAR 文件来导入 TestNG
- 编写测试类: 创建测试类,并使用TestNG的注解为测试方法配置测试环境
- 创建配置文件: 使用 Maven 构建工具的情况下,在项目中创建testng.xml配置文件,用于配置测试套件的执行方式、参数等。使用 Apache Ant 构建工具的情况下,在项目中创建build.xml配置文件,用于定义项目的构建和编译过程,包括编译源代码、运行测试等
- 运行测试: 使用TestNG运行测试。可以通过命令行、IDE插件(例如在IntelliJ IDEA或Eclipse中运行TestNG测试),或者通过构建工具(如Maven、Gradle或Apache Ant)来执行测试。
2. 导入依赖
2.1 直接导入 JAR 文件
直接在快照网站下载jar文件,放入项目 lib 目录下:
2.2 使用 Maven 构建工具
如果使用 Maven 作为项目构建工具,可以在 pom.xml 文件中添加以下依赖配置:
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>7.4.0</version> <!-- 使用最新版本 -->
<scope>test</scope>
</dependency>
2.3 使用 Apache Ant 构建工具
在 Apache Ant 构建工具中,通常使用 Ivy 作为依赖管理工具来导入依赖。在项目的根目录创建一个 ivy.xml 文件:
<ivy-module version="2.0">
<info organisation="your-organization" module="your-project" />
<dependencies>
<dependency org="org.testng" name="testng" rev="7.4.0" />
<!-- 添加其他依赖项 -->
</dependencies>
</ivy-module>
2.4 使用 Gradle 构建工具
在 build.gradle 文件添加依赖配置:
plugins {
id 'java'
}
repositories {
mavenCentral()
}
dependencies {
testImplementation 'org.testng:testng:7.4.0'
// 添加其他依赖项
}
test {
useTestNG()
}
3. 编写测试类
3.1 注解
注解(Annotation)是 Java 5 引入的一种元数据形式,它为程序元素(类、方法、字段等)添加附加信息。注解以 @
符号开头,如 @Test
、@Override
等。注解提供了对代码进行元数据标记的方式,可以用于配置、文档生成、运行时处理等。Java中的类、方法、变量、参数、包都可以被注解。
3.1.1 @Test 注解
@Test
注解用于标记一个测试方法。测试方法是实际执行测试的地方。
import org.testng.annotations.Test;
public class MyTest {
@Test
public void testMethod() {
// 这里写你的测试代码
}
}
3.1.2 生命周期注解
这些注解用于控制测试执行的生命周期:@BeforeTest
: 在所有测试方法运行之前执行,一般用来执行一些初始化操作。@AfterTest
: 在所有测试方法运行之后执行,一般用来执行一些清理操作。@BeforeClass
: 在测试类中的所有测试方法运行之前执行,这个方法在整个测试类中只会执行一次,通常用于设置测试类级别的一些预备操作,例如初始化资源、建立连接等。@AfterClass
: 在测试类中的所有测试方法运行之后执行,同样在整个测试类中只会执行一次,通常用于进行一些清理工作或资源释放。@BeforeMethod
: 在每个测试方法运行之前执行。@AfterMethod
: 在每个测试方法运行之后执行。
import org.testng.annotations.*;
public class MyTest {
@BeforeTest
public void setupBeforeTest() {
// 执行一些初始化操作
}
@AfterTest
public void cleanupAfterTest() {
// 执行一些清理操作
}
@BeforeClass
public void setupBeforeClass() {
// 这里可以进行一些初始化操作,例如设置连接,准备测试数据等
}
@AfterClass
public void cleanupAfterClass() {
// 这里可以进行一些清理工作,例如关闭连接,释放资源等
}
@BeforeMethod
public void setupBeforeMethod() {
// 在每个测试方法运行之前执行
}
@AfterMethod
public void cleanupAfterMethod() {
// 在每个测试方法运行之后执行
}
@Test
public void testMethod() {
// 这里写你的测试代码
}
}
3.1.3 @DataProvider 注解
@DataProvider
注解用于提供测试方法的参数。可以在同一个测试方法中运行多组数据。
import org.testng.annotations.Test;
import org.testng.annotations.DataProvider;
public class ParameterizedTest {
@Test(dataProvider = "testData")
public void testMethod(String param1, int param2) {
// 使用参数运行测试
}
@DataProvider(name = "testData")
public Object[][] testData() {
return new Object[][] {
{"value1", 1},
{"value2", 2},
// 添加更多测试数据
};
}
}
3.1.4 依赖注解
通过 dependsOnMethods
和 dependsOnGroups
属性可以定义测试方法之间的依赖关系。
import org.testng.annotations.Test;
public class DependencyTest {
@Test
public void setup() {
// 设置测试环境
}
@Test(dependsOnMethods = "setup")
public void testMethod() {
// 测试代码
}
}
3.1.5 @Test(groups = "groupName") 注解
分组可以帮助你组织和运行测试。可以通过分组运行指定的测试。
import org.testng.annotations.Test;
public class GroupTest {
@Test(groups = "group1")
public void testMethod1() {
// 测试代码属于 group1
}
@Test(groups = "group2")
public void testMethod2() {
// 测试代码属于 group2
}
}
3.1.6 并发注解
通过 @Test(threadPoolSize = n, invocationCount = m)
注解可以实现并发测试,让相同的测试方法运行多次或同时运行。
import org.testng.annotations.Test;
public class ParallelTest {
@Test(threadPoolSize = 5, invocationCount = 10)
public void testMethod() {
// 并发运行的测试代码
}
}
3.1.7 @Ignore 注解
在测试开发中,有时候你可能会遇到一些特殊情况,需要暂时禁用或忽略某些测试方法。这时使用 @Ignore
注解可以达到以下目的:
- 临时排除测试: 当某个测试方法因为某个 bug 或其他原因导致无法通过,但你又不希望删除这个测试方法,可以使用
@Ignore
注解进行标记,暂时排除它,使其不参与当前测试运行。 - 跳过不稳定的测试: 有些测试可能对外部环境或资源有依赖,导致它们在不同的执行环境中表现不稳定。在这种情况下,你可以通过
@Ignore
注解将这些不稳定的测试方法排除,以避免测试结果的误导。 - 跳过不适用的测试: 在某些情况下,测试方法可能仅适用于特定的场景或配置。如果当前运行环境不满足这些条件,你可以使用
@Ignore
注解来跳过这些不适用的测试。
@Test
@Ignore("这个测试方法有一个已知的 bug,正在修复中")
public void ignoredTestWithReason() {
// 测试逻辑
}
3.2 监听器
在TestNG中,监听器是一种机制,允许你在测试执行的不同生命周期事件中插入自定义行为。监听器可以捕获测试生命周期中的不同事件,例如测试开始、测试结束、测试方法执行前后等。通过使用监听器,可以在测试执行过程中执行额外的操作或记录日志,以便更好地了解测试的执行情况。
3.2.1 内置监听器
TestNG 提供了一些内置的监听器,同时也支持用户创建自定义的监听器。以下是一些常用的内置监听器:
- IInvokedMethodListener: 用于监听每个测试方法的调用。
- ISuiteListener: 用于监听整个测试套件的启动和结束。
- ITestListener: 用于监听单个测试的启动和结束。
- IReporter: 用于生成报告。
- IAnnotationTransformer: 用于在运行时修改注解。
3.2.2 自定义监听器
可以实现 TestNG 提供的监听器接口,创建自定义监听器来处理测试过程中的事件。
import org.testng.ITestListener;
import org.testng.ITestResult;
public class MyTestListener implements ITestListener {
// 实现 ITestListener 接口的方法
}
在 testng.xml
文件中注册监听器:
<listeners>
<listener class-name="path.to.MyTestListener" />
</listeners>
3.3 测试结果
在测试中,通常使用断言(Assertion)来验证测试的预期结果。断言是一种用于检查测试中特定条件的机制,如果测试完成时没有引发任何异常,或者引发了预期的异常,则测试被视为成功。如果引发了预期外的异常,从而表明测试失败。这样,测试结果中就能够体现符合预期和不符合预期的情况。
TestNG 提供了一系列的断言方法,最常用的是 assertEquals
,它比较两个值是否相等。例如:
import org.testng.Assert;
import org.testng.annotations.Test;
public class ExampleTest {
@Test
public void testAddition() {
int result = add(2, 3);
Assert.assertEquals(result, 5, "加法不正确");
}
@Test
public void testSubtraction() {
int result = subtract(5, 3);
Assert.assertEquals(result, 2, "减法不正确");
}
private int add(int a, int b) {
return a + b;
}
private int subtract(int a, int b) {
return a - b;
}
}
在这个例子中,Assert.assertEquals(result, 5, "Addition is incorrect");
语句表示预期 result 的值应该是 5,如果不是,就会引发断言失败的异常,并且在报告中会显示 "加法不正确"。
3.4 编写测试用例
在 TestNG 中,测试用例(Test Case)是通过 Java 类中的测试方法(Test Method)来定义的。这些测试方法通过 @Test 注解进行标记。以下是一个简单的示例:
import org.testng.Assert;
import org.testng.annotations.*;
public class BasicTestNGExamples {
// 在测试类中的所有测试方法运行之前执行
@BeforeClass
public void setUpClass() {
System.out.println("执行所有测试方法前的准备工作");
}
// 在测试类中的所有测试方法运行之后执行
@AfterClass
public void tearDownClass() {
System.out.println("执行所有测试方法后的清理工作");
}
// 在每个测试方法运行之前执行
@BeforeMethod
public void setUp() {
System.out.println("执行每个测试方法前的准备工作");
}
// 在每个测试方法运行之后执行
@AfterMethod
public void tearDown() {
System.out.println("执行每个测试方法后的清理工作");
}
// 测试方法1
@Test(priority = 1, description = "这是第一个测试方法")
public void testMethod1() {
System.out.println("执行测试方法1");
Assert.assertTrue(true, "断言失败信息");
}
// 测试方法2
@Test(priority = 2, description = "这是第二个测试方法")
@Parameters("input")
public void testMethod2(String input) {
System.out.println("执行测试方法2");
Assert.assertEquals(input, "Hello", "断言失败信息");
}
// 忽略测试方法
@Test(priority = 3, description = "这个方法会被忽略")
@Ignore("忽略的原因")
public void ignoredTest() {
System.out.println("这个方法不会被执行");
}
// 依赖其他测试方法的测试
@Test(priority = 4, description = "依赖其他测试方法")
@DependsOnMethods("testMethod2")
public void dependentTest() {
System.out.println("这个方法依赖于 testMethod2");
}
// 参数化测试
@Test(priority = 5, description = "参数化测试", dataProvider = "dataProvider")
public void parameterizedTest(String input1, String input2, int expectedResult) {
System.out.println("参数1: " + input1 + ", 参数2: " + input2);
int result = Integer.parseInt(input1) + Integer.parseInt(input2);
Assert.assertEquals(result, expectedResult, "断言失败信息");
}
// 数据提供者方法
@DataProvider(name = "dataProvider")
public Object[][] provideData() {
return new Object[][]{
{"1", "2", 3}, // 第一组测试数据
{"-2", "5", 3}, // 第二组
{"0", "0", 0} // 第三组
};
}
}
这个示例涵盖了一些基础的 TestNG 注解和场景,包括:
- @BeforeClass 和 @AfterClass 注解的使用。
- @BeforeMethod 和 @AfterMethod 注解的使用。
- @Test 注解的基本用法,包括设置测试方法的优先级、描述、依赖关系等。
- @Ignore 注解用于忽略某个测试方法。
- @DependsOnMethods 注解用于定义测试方法之间的依赖关系。
- 数据参数化的实现,使用 @DataProvider 注解提供测试数据。
4. 创建配置文件
4.1 使用 Maven
在项目的根目录,创建一个名为 testng.xml 的文件。文件内容格式如下:
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
<suite name="MyTestSuite">
<test name="MyTestClass">
<classes>
<!-- 指定测试类 -->
<class name="your.package.name.TestClass" />
</classes>
</test>
</suite>
<suite>
元素用于定义整个测试套件,必须包含name
属性,该属性表示测试套件的名称,是为了标识测试套件的名称而设定的,它可以是任何字符串,只要它符合 XML 的命名规则即可。<suite>
元素内可以包含多个<test>
元素。<test>
元素用于定义一个具体的测试,必须包含 name 属性,表示测试的名称。它可以包含多个<classes>
、<packages>
、<methods>
或<parameter>
元素,用于指定包含在该测试中的测试类、包、方法或参数。
请确保将 "your.package.name" 替换为 TestClass 所在的实际包名。如果您想配置 TestNG 生成测试报告,可以在 <suite>
元素中添加 <listeners>
元素,并指定相应的报告生成器。以下是一个示例:
<suite name="MyTestSuite">
<!-- ... 其他配置 ... -->
<!-- 设置生成测试报告 -->
<listeners>
<listener class-name="org.testng.reporters.EmailableReporter"/>
<listener class-name="org.testng.reporters.JUnitReportReporter"/>
<listener class-name="org.testng.reporters.TestHTMLReporter"/>
<listener class-name="org.testng.reporters.XMLReporter"/>
</listeners>
<!-- 设置测试报告输出目录 -->
<parameter name="output-directory" value="test-output" />
</suite>
4.2 使用 Apache Ant
Apache Ant 使用 XML 文件作为配置文件,通常命名为 build.xml。以下是一个简单的示例,演示了一个包含编译、运行测试和生成报告等任务的 build.xml 文件:
<project name="MyProject" default="run-tests" basedir=".">
<!-- 定义属性 -->
<property name="src.dir" value="src" />
<property name="build.dir" value="build" />
<property name="test.dir" value="test" />
<property name="report.dir" value="test-output" />
<!-- 定义路径 -->
<path id="classpath">
<pathelement location="${build.dir}" />
<pathelement path="${java.class.path}" />
</path>
<!-- 编译源代码 -->
<target name="compile">
<mkdir dir="${build.dir}" />
<javac srcdir="${src.dir}" destdir="${build.dir}" includeantruntime="false">
<classpath refid="classpath" />
</javac>
</target>
<!-- 运行测试 -->
<target name="run-tests" depends="compile">
<testng classpathref="classpath">
<xmlfileset dir="${basedir}" includes="testng.xml" />
</testng>
</target>
<!-- 生成报告 -->
<target name="generate-report" depends="run-tests">
<mkdir dir="${report.dir}" />
<testng-results outputDir="${report.dir}" />
</target>
</project>
在这个示例中,Ant 项目包含三个主要任务:compile、run-tests 和 generate-report。
- compile 任务用于编译源代码
- run-tests 任务用于运行测试
- generate-report 任务用于生成测试报告
这些任务的执行顺序由依赖关系定义
5. 运行测试
5.1 使用 Maven
通过 Maven 插件运行 TestNG 测试。在 Maven 项目中,你可以使用以下命令:
mvn test
5.2 使用 TestNG 的命令行运行器
通过 TestNG 提供的命令行运行器运行测试。例如:
java -cp "path/to/testng.jar:path/to/your/classes" org.testng.TestNG path/to/testng.xml
5.3 使用 Apache Ant
在Apache Ant项目中,编辑好build.xml后执行:
ant runTests
6. 使用实例
6.1 自己写的API登录功能测试
使用 TestNG 对登录功能进行测试,采用参数化测试,不考虑大文件读取。配置 Maven 构建环境:
6.1.1 测试环境
登录地址为: /login
请求方法: POST
数据格式: json
参数: {"Username:"",Password:""}
预期结果:
登录成功响应200并返回:
{ "message": "xxxx", "status": "True" }
登录失败响应200并返回:
{ "message": "用户名或密码不正确", "status": "False" }
Json格式错误响应500并返回:
{ "message": "XXX", "status": "Error" }
- 其他错误情况未知
6.1.2 编写测试类
使用 Maven 构建项目,编码实现 LoginTest 测试类和测试方法。
6.1.2.1 添加依赖
使用 TestNG 6.14.3 实现测试类,使用 json 解析登录的 json 数据,pom.xml文件内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>LoginTest</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>6.14.3</version>
</dependency>
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20210307</version>
</dependency>
</dependencies>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
</project>
6.1.2.2 编码实现测试类
定义 LoginTest 类,使用user.txt(m行)和pass.txt(n行)文件中的字符串做为登录的参数,使用数据提供器生成 m*n
组测试数据进行测试,LoginTest.java文件内容如下:
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import org.testng.Assert;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import org.json.JSONObject;
public class LoginTest {
// 数据提供器,从user.txt和pass.txt中提供测试数据
@DataProvider(name = "userData")
public Object[][] provideUserData() {
List<Object[]> userDataList = new ArrayList<>();
// 从文件中读取用户名和密码
List<String> usernames = readLinesFromFile("user.txt");
List<String> passwords = readLinesFromFile("pass.txt");
// 组合用户名和密码,形成测试数据
for (String username : usernames) {
for (String password : passwords) {
userDataList.add(new Object[]{username, password});
}
}
return userDataList.toArray(new Object[0][0]);
}
// 测试方法,用于测试登录接口
@Test(dataProvider = "userData", description = "测试登录接口")
public void testLogin(String username, String password) {
String apiUrl = "http://127.0.0.1/login";
String requestBody = "{\"Username\":\"" + username + "\", \"Password\":\"" + password + "\"}";
try {
// 创建URL对象
URL url = new URL(apiUrl);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("POST");
connection.setDoOutput(true);
// 设置请求头,指定 Content-Type 为 application/json
connection.setRequestProperty("Content-Type", "application/json");
// 将请求体写入输出流
try (OutputStream os = connection.getOutputStream()) {
byte[] input = requestBody.getBytes("utf-8");
os.write(input, 0, input.length);
}
// 读取响应
try (BufferedReader br = new BufferedReader(new java.io.InputStreamReader(connection.getInputStream(), "utf-8"))) {
StringBuilder response = new StringBuilder();
String responseLine;
while ((responseLine = br.readLine()) != null) {
response.append(responseLine.trim());
}
// 解析响应
String status = getStatusFromResponse(response.toString());
String message = getMessageFromResponse(response.toString());
// 验证响应
if ("True".equals(status)) {
Assert.assertTrue(response.toString().contains("\"message\""), "登录成功响应缺少message字段");
} else if ("False".equals(status)) {
Assert.assertTrue(response.toString().contains("\"message\""), "登录失败响应缺少message字段");
} else if ("Error".equals(status)) {
Assert.assertTrue(response.toString().contains("\"message\""), "登录请求发生错误: " + message);
} else {
Assert.fail("未处理的响应情况: " + response.toString());
}
}
} catch (IOException e) {
e.printStackTrace();
Assert.fail("登录请求期间出现异常: " + e.getMessage());
}
}
// 从文件中读取每一行数据
private List<String> readLinesFromFile(String fileName) {
List<String> lines = new ArrayList<>();
try (BufferedReader reader = new BufferedReader(new FileReader(fileName))) {
String line;
while ((line = reader.readLine()) != null) {
lines.add(line);
}
} catch (IOException e) {
e.printStackTrace();
}
return lines;
}
// 从响应中获取状态字段
private String getStatusFromResponse(String response) {
try {
JSONObject jsonResponse = new JSONObject(response);
return jsonResponse.optString("status", "");
} catch (Exception e) {
e.printStackTrace();
return "";
}
}
// 从响应中获取消息字段
private String getMessageFromResponse(String response) {
try {
JSONObject jsonResponse = new JSONObject(response);
return jsonResponse.optString("message", "");
} catch (Exception e) {
e.printStackTrace();
return "";
}
}
}
6.1.3 创建配置文件
在项目的根目录,创建一个名为 testng.xml 的文件,内容如下:
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
<suite name="MyTestSuite">
<test name="MyTestClass">
<classes>
<!-- 指定测试类 -->
<class name="LoginTest" />
</classes>
</test>
<!-- 设置生成测试报告 -->
<listeners>
<listener class-name="org.testng.reporters.EmailableReporter"/>
<listener class-name="org.testng.reporters.JUnitReportReporter"/>
<listener class-name="org.testng.reporters.TestHTMLReporter"/>
<listener class-name="org.testng.reporters.XMLReporter"/>
</listeners>
<!-- 设置测试报告输出目录 -->
<parameter name="output-directory" value="test-output" />
</suite>
6.1.4 运行
6.1.5 报告输出
6.2 DVWA SQL注入测试
6.2.1 测试环境
漏洞环境使用Dvwa的Low级别测试SQL注入
Payload通过自定义sql.txt实现,内容如下:
'
'or'1'='1
\\
6.2.2 编写测试类并运行
代码如下:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.testng.Assert;
import org.testng.annotations.Test;
public class DvwaTest {
private String dvwaURL = "http://dvwa.com";
private String sqlInjectionEndpoint = "/vulnerabilities/sqli/";
private String payloadDirectory = "payload";
@Test
public void testSQLInjection() throws IOException {
// 从文件中读取SQL注入payload
List<String> sqlInjectionPayloads = readPayloadsFromFile("sql.txt");
// 构造HTTP客户端
CloseableHttpClient httpClient = HttpClients.createDefault();
// 迭代每个测试输入
for (String sqlInjectionPayload : sqlInjectionPayloads) {
// 构造GET请求
String encodedPayload = urlEncode(sqlInjectionPayload);
String fullUrl = dvwaURL + sqlInjectionEndpoint + "?id=" + encodedPayload + "&Submit=Submit#";
HttpGet httpGet = new HttpGet(fullUrl);
// 执行GET请求
try (CloseableHttpResponse response = httpClient.execute(httpGet)) {
// 读取响应内容
HttpEntity entity = response.getEntity();
String responseBody = readResponse(entity);
// 输出实际响应内容,方便调试
System.out.println("SQL注入Payload: " + sqlInjectionPayload);
System.out.println("实际响应内容: " + responseBody);
// 检查页面是否包含任何数据库的SQL语句报错信息
boolean isVulnerabilityExploited = !responseBody.contains("SQL syntax");
// 断言实际响应内容中不包含SQL报错语句
Assert.assertFalse(responseBody.contains("SQL syntax"), "返回页面包含SQL报错语句:" + responseBody);
System.out.println("测试结果: " + (isVulnerabilityExploited ? "通过" : "不通过"));
System.out.println();
}
}
// 关闭HTTP客户端
httpClient.close();
}
// 从文件中读取多个payload的辅助方法
private List<String> readPayloadsFromFile(String fileName) throws IOException {
String filePath = Paths.get(payloadDirectory, fileName).toString();
return Files.readAllLines(Paths.get(filePath));
}
// URL编码辅助方法
private String urlEncode(String value) throws UnsupportedEncodingException {
return URLEncoder.encode(value, StandardCharsets.UTF_8.toString());
}
// 读取响应内容的辅助方法
private String readResponse(HttpEntity entity) throws IOException {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(entity.getContent()))) {
StringBuilder result = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
result.append(line);
}
return result.toString();
}
}
}
存在SQL报错语句时,测试不通过:
不存在SQL报错语句时,测试通过:
7. 疑问解答
7.1 TestNG主要用于测试什么
TestNG(Test Next Generation)是一个用于执行单元测试、集成测试和功能测试的测试框架。它主要用于测试Java应用程序,但也可以用于测试其他类型的应用程序。以下是 TestNG 的一些主要用途:
- 单元测试: TestNG 支持执行单元测试,确保代码中的各个单元(方法、类等)按照预期工作。这有助于发现和修复代码中的错误,确保每个单元都按照设计进行测试。
- 集成测试: TestNG 提供了对集成测试的强大支持。在集成测试中,多个单元被组合在一起以验证它们一起工作的方式。这有助于检测系统中组件之间的交互问题。
- 功能测试: TestNG 支持执行功能测试,确保整个应用程序在各种情况下都能够按照用户需求正常运行。功能测试涉及对应用程序的端到端测试,以确保其功能完整性。
- 并发测试: TestNG 具有对并发测试的内置支持。这意味着测试可以在多个线程中并行执行,加快测试执行速度。
- 数据驱动测试: TestNG 支持数据驱动测试,允许在不同的输入数据集上运行相同的测试用例。这对于对不同数据集进行测试和验证应用程序行为很有用。
- 配置管理: TestNG 提供了注解(Annotations)和配置文件的方式来管理测试用例的配置,例如测试的顺序、依赖关系、测试套件等。
7.2 TestNG是怎么确定测试执行成功、失败或预期外的异常的
TestNG 确定测试执行状态的方式主要基于断言和异常的处理。以下是 TestNG 如何判断测试执行状态的一般流程:
- 成功(Success): 如果在测试方法中的所有断言成功执行,即条件满足,没有抛出 AssertionError 异常,那么 TestNG 将标记该测试为成功。TestNG 的断言方法如 Assert.assertEquals()、Assert.assertTrue() 等用于判断测试的实际结果是否符合预期。
- 失败(Failure): 如果测试方法中的任何断言失败,即条件不满足,抛出了 AssertionError 异常,TestNG 将标记该测试为失败。测试失败意味着测试中的某些期望结果与实际结果不匹配。
- 预期外的异常(Unexpected Exception): 如果测试方法抛出了除了 AssertionError 以外的异常,TestNG 也会将测试标记为失败。这包括在测试方法中抛出的任何未被捕获的异常。
- 跳过(Skipped): 通过 @Test(enabled = false) 或其他一些跳过机制,可以标记测试方法或依赖的测试方法不执行,TestNG 将标记测试为跳过。
- 超时(Timeout): 如果测试方法在规定的时间内没有完成,可以通过 @Test(timeOut = ...) 设置超时时间,TestNG 将标记测试为超时。
还可以通过监听器捕获测试执行期间的各种事件,例如测试开始、测试结束、测试成功、测试失败等。
7.3 TestNG 可以测试和无法测试的适用于API的漏洞类型有哪些?
TestNG可以测试的漏洞类型:
- 用户名枚举
- 暴力破解
- 未授权访问
- 敏感数据泄露
- 有回显类漏洞,如有回显的SQL注入. 有回显的XSS. 有回显的命令注入
- 路径穿越
- 任意文件上传/覆盖
- 任意文件读取/下载
- 参数污染
TestNG无法测试的漏洞类型:
- 无回显类漏洞,如基于bool的SQL注入. 基于time的SQL注入. 无回显XSS. 无回显命令注入. SSRF
- 需要用户交互的漏洞,如CSRF
- 特殊显示的漏洞,如返回文件上传失败但成功上传任意文件
- 需要回调请求的漏洞,如XXE. 不安全的反序列化
- 逻辑漏洞,如复杂的越权. 复杂的业务流程
- 拒绝服务
Comments | NOTHING