TestNG框架学习笔记


1. 介绍

官方网站
GitHub地址

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 编写和执行测试的基本流程如下:

  1. 导入依赖: 在项目中使用 Maven 或其他构建工具,导入 TestNG 的依赖,或下载 TestNG JAR 文件来导入 TestNG
  2. 编写测试类: 创建测试类,并使用TestNG的注解为测试方法配置测试环境
  3. 创建配置文件: 使用 Maven 构建工具的情况下,在项目中创建testng.xml配置文件,用于配置测试套件的执行方式、参数等。使用 Apache Ant 构建工具的情况下,在项目中创建build.xml配置文件,用于定义项目的构建和编译过程,包括编译源代码、运行测试等
  4. 运行测试: 使用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 依赖注解

通过 dependsOnMethodsdependsOnGroups 属性可以定义测试方法之间的依赖关系。

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:""}
预期结果:

  1. 登录成功响应200并返回:

    {
     "message": "xxxx",
     "status": "True"
    }
  2. 登录失败响应200并返回:

    {
     "message": "用户名或密码不正确",
     "status": "False"
    }
  3. Json格式错误响应500并返回:

    {
     "message": "XXX",
     "status": "Error"
    }
  4. 其他错误情况未知

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 运行

使用 IEDA 集成 IDE,直接运行即可:

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可以测试的漏洞类型:

  1. 用户名枚举
  2. 暴力破解
  3. 未授权访问
  4. 敏感数据泄露
  5. 有回显类漏洞,如有回显的SQL注入. 有回显的XSS. 有回显的命令注入
  6. 路径穿越
  7. 任意文件上传/覆盖
  8. 任意文件读取/下载
  9. 参数污染

TestNG无法测试的漏洞类型:

  1. 无回显类漏洞,如基于bool的SQL注入. 基于time的SQL注入. 无回显XSS. 无回显命令注入. SSRF
  2. 需要用户交互的漏洞,如CSRF
  3. 特殊显示的漏洞,如返回文件上传失败但成功上传任意文件
  4. 需要回调请求的漏洞,如XXE. 不安全的反序列化
  5. 逻辑漏洞,如复杂的越权. 复杂的业务流程
  6. 拒绝服务

声明:Hack All Sec的博客|版权所有,违者必究|如未注明,均为原创|本网站采用BY-NC-SA协议进行授权

转载:转载请注明原文链接 - TestNG框架学习笔记


Hacker perspective for security