白盒测试 TestNG 实用教程

TesterHome小助手 · 2022年05月07日 · 最后由 TesterHome小助手 回复于 2022年06月28日 · 3956 次阅读

【1、TestNg 安装、运行、报告】

1、Idea 生成 maven 项目,启动 File--New--Project

2、在 maven 项目中,在 pom 文件中引入 TestNG 依赖(maven 项目的好处是,不再需要我们再单独做 jar 包引用)

<dependency>
    <groupId>org.testng</groupId>
    <artifactId>testng</artifactId>
    <version>6.14.3</version>
</dependency>

3、新建一个测试类

import org.testng.annotations.Test;

public class testcase {
    @Test
    public void test1() {
        System.out.println("测试用例1被执行");
    }
}

4、来执行我们的测试用例

执行 testng 框架的测试用例,会生成测试报告 ,但是在 idea 中看不到,可以更改如下配置,执行完后,查看测试报告

选择要执行的用例,更改默认配置

再次执行测试用例后,可以查看到自动生成的测试报告

5、运行 TestNG 测试脚本有两种方式:一种是直接通过 IDE 运行,另一种是从命令行运行(通过使用 xml 配置文件)。
通过执行 xml 文件或者命令行执行 xml 配置进行用例执行。

idea 也有一个 testng 插件,用于辅助生成 xml 文件

在对应的类,右键可以辅助生成 testng.xml 文件

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
<suite name="All Test Suite">
    <test verbose="2" preserve-order="true" name="E:/testngProject/src/main/java/testcase.java">
        <classes>
            <class name="testcase"></class>
        </classes>
    </test>
</suite>

在我们的工程中,右键直接执行 testng.xml。

testng 除了上述执行方式外,还提供了 java 方法运行、maven 运行方式,非常灵活的支持自定义以及 CI/CD:

使用 main 方法执行用例,同时也可以设置报告输出目录

import org.testng.TestNG;

public class testngRun {

    public static void main(String[] args){
        TestNG tng = new TestNG();
        tng.setTestClasses(new Class[] {testcase.class,testcase2.class});
        tng.setOutputDirectory("E:\\testngProject\\report");
        tng.run();
    }
}

使用 main 方法指定测试套件 xml,执行测试用例

import org.testng.TestNG;
import org.testng.xml.XmlSuite;

import java.util.ArrayList;
import java.util.List;

public class testngXmlRun {

    public static void main(String[] args){
        TestNG tng = new TestNG();

        //配置xml执行文件路径
        XmlSuite xmlSuite = new XmlSuite();
        List<String> filepath = new ArrayList<String>();
            filepath.add("E:\\testngProject\\testng.xml");
        xmlSuite.setSuiteFiles(filepath);
        //设置xml执行套件
        List<XmlSuite> suites = new ArrayList<XmlSuite>();
            suites.add(xmlSuite);

        tng.setXmlSuites(suites);
        tng.setTestClasses(new Class[] {testcase.class,testcase2.class});
        tng.setOutputDirectory("E:\\testngProject\\report");
        tng.run();
    }
}

使用 maven 执行用例
在 pom 文件添加 Surefire 插件配置

<build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>3.0.0-M6</version>
                <configuration>
                    <suiteXmlFiles>
                        <suiteXmlFile>${suiteXmlFile}</suiteXmlFile>
                    </suiteXmlFiles>
                </configuration>
            </plugin>
        </plugins>
    </build>

这里将套件名称定义为了 ${suiteXmlFile} ,可以方便我们在后续执行中,调用不同的 xml 测试套件

通过 mvn 直接调用 testng 测试用例

mvn clean test -DsuiteXmlFile=testng.xml

执行完成后,也会生成对应的测试报告

TestNG 实用教程【2、TestNg 注解】

TestNG 注解
这些是用于控制方法流的程序或业务逻辑。它们在 TestNG 中起着非常重要的作用。这些注释根据项目要求会有所不同,但注释的流程在每个程序中保持不变。

注解 描述
@BeforeSuite 注释的方法将在这个套件中的所有测试运行之前被运行
@AfterSuite: 注释的方法将在该套件中的所有测试运行后运行
@BeforeTest 注释的方法将在任何属于标签内的类的测试方法运行之前运行
@AfterTest 注释的方法将在所有属于标签内的类的测试方法运行后运行
@BeforeGroups 这个配置方法将在之前运行的组的列表。该方法保证在属于这些组的第一个测试方法被调用前不久运行
@AfterGroups 这个配置方法将在之后运行的组的列表。该方法保证在最后一个属于这些组的测试方法被调用后不久运行
@BeforeClass 该注释方法将在当前类的第一个测试方法被调用之前运行
@AfterClass 注释的方法将在当前类的所有测试方法运行后运行
@BeforeMethod 注释的方法将在每个测试方法之前运行
@AfterMethod 被注解的方法将在每个测试方法之后运行

注解的工作流程
在执行 TestNG.xml 文件时,注解将按这样的顺序执行。
Before Suite-> Before Test-> Before Class->Before Method-> Test -> After Method-> After Class-> After Test-> After Suite

@Test 标记一个类或一个方法作为测试的一部分。
他有非常多的其他属性,能够帮助我们进行有效的测试。下面列举了一些常用的属性

属性 描述
alwaysRun 如果设置为 true,这个测试方法将总是被运行,即使它依赖的方法执行失败
dataProvider 这个测试方法,提供参数数据的方法名称
dataProviderClass 如果指定了这个属性,数据方法需要静态化
dependsOnGroups 这个方法依赖的组的列表
dependsOnMethods 这个方法依赖的方法列表
description 这个方法的描述
enabled 这个类/方法上的方法是否被启用
expectedExceptions 预计测试方法会抛出的异常列表。如果没有抛出任何异常或与此列表中的异常不同,这个测试将被标记为失败
groups 这个类/方法所属的组的列表
invocationCount 这个方法应该被调用的次数
invocationTimeOut 这个测试应该在所有 invocationcount 的累积时间内花费的最大毫秒数。如果没有指定 invocationCount,这个属性将被忽略。
priority 优先级 这个测试方法的优先级。较低的优先级将被首先安排。
successPercentage 该方法的预期成功百分比
singleThreaded 如果设置为 true,这个测试类的所有方法都保证在同一个线程中运行,即使当前测试正在以 parallel="methods "运行。这个属性只能在类的层面上使用,如果在方法层面上使用,它将被忽略。注意:这个属性曾经被称为 sequential(现在已经废弃了)。
timeOut 这个测试应该花费的最大毫秒数
threadPoolSize 这个方法的线程池的大小。该方法将从多个线程中调用,由 invocationCount 指定,如果没有指定 invocationCount,这个属性会被忽略

alwaysRun 样例
当 alwaysRun 为 ture 时,就算 errMethods 方法执行失败,也会执行。 为 flase 时,由于 errMethods 执行失败,test1 未被执行

public class testcase {

    @Test
    public void errMethods() {
        int a = 1;
        int b = 0 ;
        System.out.println("我是被依赖的方法"+a/b);
    }

    @Test(alwaysRun = true,dependsOnMethods = {"errMethods"})
    public void test1() {
        System.out.println("测试用例1被执行");

    }
}

dataProvider 样例
执行用例后,可以看到分别会打印:我的姓名是张三;我的姓名是李四

@DataProvider(name = "namedata")
   public Object[][] createData1() {
       return new Object[][] {
               { "张三" },
               { "李四"},
       };
   }

   @Test(dataProvider = "namedata")
   public void test1(String name) {
       System.out.println("我的姓名是" +name);
   }

dataProviderClass 样例
还可以在找定的类中,查找参数数据源,执行用例可以发现,打印的内容为:我的姓名是王五;我的姓名是赵六

@DataProvider(name = "namedata")
   public Object[][] createData1() {
       return new Object[][] {
               { "张三" },
               { "李四"},
       };
   }

   @Test(dataProvider = "namedata",dataProviderClass = nameData.class)
   public void test1(String name) {
       System.out.println("我的姓名是" +name);
   }
public class nameData {
    @DataProvider(name = "namedata")
    public static Object[][] createData1() {
        return new Object[][] {
                { "王五" },
                { "赵六"},
        };
    }
}

dependsOnGroups 样例
这个属性依赖于组列表,测试方法只有在依赖组执行后才会开始执行,如果依赖的组中的任何测试失败,那么它将跳过该测试
当将 fun2 打开后,会发现 test1 阻塞了执行

@Test(groups= "group1")
    public void fun1(){
        System.out.println("我fun1属于工作组group1");
    }

    //@Test(groups= "group1")
   // public void fun2(){
      //  int a = 0;
       // int b = a;
      //  System.out.println("我是fun2属于工作组group1"+b/a);
  //  }

    @Test(dependsOnGroups = "group1")
    public void test1() {
        System.out.println("我是用例test1");
    }

*dependsOnMethods *
这个属性依赖的方法列表,同上一样,只有在依赖方法执行后才会开始执行,如果依赖的方法中的任何测试失败,那么它将跳过该测试

expectedExceptions 样例
这个方法就比较有意思了
提前预计测试方法会抛出的异常。如果没有标识的异常,测试将被标记为失败

@Test(expectedExceptions = {ArithmeticException.class})
    public void test2(){
        int a = 0;
        int b = a;
        System.out.println("我是test2"+b/a);
    }

invocationCount 样例
运行一下来看,可以明显看出这个方法执行了 2 次

@Test(invocationCount = 2)
   public void test1() {
       System.out.println("我是用例test1");
   }

priority 样例
这也是一个非常有用的属性,可以帮助我们调置方法执行的优先顺序。test1 没有分配数字,它的默认优先级为 0。
运行结果可以看到:我是用例 test1--》我是用例 test2--》我是用例 test3
是按照升序进行执行的

@Test()
    public void test1() {
        System.out.println("我是用例test1");
    }

    @Test(priority = 1)
    public void test2() {
        System.out.println("我是用例test2");
    }

    @Test(priority = 2)
    public void test3() {
        System.out.println("我是用例test3");
    }

@DataProvider:标记一个方法为一个测试方法提供数据。被注解的方法必须返回一个 Object[][],其中每个 Object[] 可以被分配给测试方法的参数列表。想要从这个 DataProvider 中接收数据的@Test方法需要使用一个与这个注解的名称相等的 DataProvider 名称。。
可以参照我们上面的
dataProvider 样例
执行用例后,可以看到分别会打印:我的姓名是张三;我的姓名是李四

@DataProvider(name = "namedata")
   public Object[][] createData1() {
       return new Object[][] {
               { "张三" },
               { "李四"},
       };
   }

   @Test(dataProvider = "namedata")
   public void test1(String name) {
       System.out.println("我的姓名是" +name);
   }

@Factory 标记一个方法为工厂,返回将被 TestNG 作为测试类使用的对象。该方法必须返回 Object[]。

执行 factoryTest 类时,能够发现我们可以使用工厂类组织各种不同数据,进行多次测试
当然使用单个测试类运行多个测试类时,TestNG Factory 非常有用。

public class factoryTest {
    @Factory()
    public Object[] createUsernames() {
        Object[] result = new Object[10];
        for (int i = 0; i < 10; i++) {
            result[i] = new testcase("张三"+i); //在这里可以加入多个测试
        }
        return result;
    }
}
public class testcase {

    private String usernames;
    public testcase(String username) {
        usernames = username;
    }

    @Test()
    public void test1() {
        System.out.println("我的名字是:"+usernames);
    }
}

TestNG 实用教程【3、TestNg 监听器】

@Listeners 允许在一个测试类上定义监听器。
简单来说,监听用例中定义的事件。也能够根据事件创建日志和自定义 TestNG 报告。

有几个接口允许修改 TestNG 的行为,这些接口被广泛称为 "TestNG 监听器"。

下面是几个监听器。

接口 说明
IAnnotationTransformer TestNG 将调用此方法,修改从测试类读取的 TestNG 注释。可以操作@Test注解的内容
IAnnotationTransformer2 可以操作@Configuration标注、@DataProvider标注和@Factory标注
IInvokedMethodListener 在 TestNG 调用方法之前和之后调用的监听器。这个监听器只会被配置和测试方法调用
IMethodInterceptor 用于更改 TestNG 即将运行的测试方法列表
IReporter 测试报告监听器,可以获取用例执行结果
ISuiteListener 测试套件的监听器
ITestListener 测试运行的监听器

测试用例代码

@Listeners({MyListener.class})
public class testcase {
    @Test(priority = 1)
    public void test1(){
        System.out.println("我是test1");
    }
}

IAnnotationTransformer 样例
可以操作@test注解的内容,下面代码可以看出,test1() 的 priority = 1,运行过程中将 priority 设置为了 3

public class MyListener implements IAnnotationTransformer {

    public void transform(ITestAnnotation iTestAnnotation, Class aClass, Constructor constructor, Method method) {

        System.out.println("当前用例优先级别为:"+iTestAnnotation.getPriority());
        iTestAnnotation.setPriority(3);
        System.out.println("重新设置后的优先级别为:"+iTestAnnotation.getPriority());  
    }
}

ISuiteListener 样例
测试套件的监听器,有两个主要方法:
onFinish 在运行所有测试套件后调用此方法。
onStart 在启动之前调用此方法。

运行过程中我们可以看到执行顺序:用例执行前调用--》我是 test1--》用例执行完成后调用

public class MyListener implements ISuiteListener {


    public void onStart(ISuite iSuite) {
        System.out.println("用例执行前调用");
    }

    public void onFinish(ISuite iSuite) {
        System.out.println("用例执行完成后调用");
    }
}

ITestListener 样例
测试运行的监听器,有七个主要方法:
onFinish 在所有测试运行并调用所有配置方法后调用
onStart 在实例化测试类之后和调用任何配置方法之前调用
onTestFailedButWithinSuccessPercentage 每次方法失败时调用
onTestFailure 每次测试失败时调用
onTestSkipped 每次跳过测试时调用
onTestStart 每次在调用测试之前调用
onTestSuccess 每次测试成功时调用

IMethodInterceptor 样例
用于更改 TestNG 即将运行的测试方法列表,只有没有依赖项且不依赖于任何其他测试方法的方法才会传入参数。TestNG 将按照返回值中的相同顺序运行这些方法。

对 testcase 进行执行顺序混乱后,可以发现,用例执行顺序变更了,这里我们可以重新调整我们用例执行顺序

public class MyListener implements IMethodInterceptor {
    public List<IMethodInstance> intercept(List<IMethodInstance> list, ITestContext iTestContext) {
        Collections.shuffle(list); // 混乱list
        return list;
    }
}
@Listeners({MyListener.class})
public class testcase {

    @Test
    public void testa(){
        System.out.println("我是testa");
    }

    @Test
    public void testc(){
        System.out.println("我是testc");
    }

    @Test
    public void testb(){
        System.out.println("我是testb");
    }
}

IInvokedMethodListener 样例
在 TestNG 调用方法之前和之后调用的监听器。这个监听器只会被配置和测试方法调用 ,有两个方法:
afterInvocation 调用前执行
beforeInvocation 调用后执行

运行过程中我们可以看到执行顺序:调用前执行--》我是 test1--》调用后执行

public class MyListener implements IInvokedMethodListener {

    public void beforeInvocation(IInvokedMethod iInvokedMethod, ITestResult iTestResult) {
        System.out.println("调用前执行");
    }

    public void afterInvocation(IInvokedMethod iInvokedMethod, ITestResult iTestResult) {
        System.out.println("调用后执行");
    }
}

IReporter 样例
测试报告监听器,可以获取用例执行结果,通过这些数据,我们也可以用于自定义生成测试报告
运行后可以获取的,所有成功和失败的用例信息,同时也有非常多的方法,获取结果信息(这里就不一一介绍了,感兴趣的可以查看对应 api)

public class MyListener implements IReporter {


    public void generateReport(List<XmlSuite> list, List<ISuite> list1, String s) {

        for (ISuite x :list1) {
            Map<String, ISuiteResult> xResults = x.getResults();
            for (ISuiteResult isresult : xResults.values()) {
                ITestContext itx = isresult.getTestContext();
                System.out.println("获取执行成功的用例数据:"+itx.getPassedTests());
                System.out.println("获取执行失败的用例数据:"+itx.getFailedTests());
            }

        }
    }
}

注意
光配置执行过程中,会发现未生效,需要对运行的用例进行监听器配置

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
<suite name="All Test Suite">
    <listeners>
        <listener class-name="MyListener" />
    </listeners>

    <test verbose="2" preserve-order="true" name="testcaseone">
        <classes >
            <class name="testcase"></class>
            <class name="testcase2"></class>
        </classes>
    </test>
</suite>

TestNG 实用教程【4、如何使用 TestNG.Xml 文件】

常用属性说明


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
<!--suite测试套件为根路径仅允许出现1次是多个test测试用例的集合以下为各属性含义及取值
    @name 必填标记suite的名称
    @parallel 选填是否多线程并发运行测试可选值(false | methods | tests | classes | instances)默认 "false"
    @thread-count 选填填写值为正整数当为并发执行时的线程池数量默认为"5"
    @configfailurepolicy 一旦Before/After Class/Methods这些方法失败后是继续执行测试还是跳过测试可选值 (skip | continue)默认"skip
    @time-out 为具体执行单元设定一个超时时间,具体参照parallel的执行单元设置;单位为毫秒
    @skipfailedinvocationcounts 是否跳过失败的调用,可选值(true | false),默认"false"
    @data-provider-thread-count 并发执行时data-provider的线程池数量,默认为"10"
    @object-factory 一个实现IObjectFactory接口的类,用来实例测试对象
    @allow-return-values="true" 是否允许返回函数值,可选值(true | false),默认"false"
    @preserve-order:顺序执行开关,可选值(true | false) "true"
    @group-by-instances:是否按实例分组,可选值(true | false) "false"
    -->
<suite name="suitename"  parallel="false" thread-count="5" configfailurepolicy="skip"
       annotations="javadoc" time-out="10000" skipfailedinvocationcounts="true" data-provider-thread-count="5"
       object-factory="classname" allow-return-values="true" preserve-order="true" group-by-instances="false">

    <!--可以执行多个suite,@path 必填,欲引用的suitefile的绝对路径-->
    <suite-files>
        <suite-file path="/path/to/suitefile1"></suite-file>
    </suite-files>
    <!--全局参数,@name和@value必填,分别为参数名和参数值-->
    <parameter name="par1" value="value1"></parameter>
    <parameter name="par2" value="value2"></parameter>

    <!--方法选择器,在suite/test中增加需要额外执行的类(根据父标签而定),及安排执行优先级-->
    <method-selectors>
        <method-selector>
            <!--
                @name 必填
                @priority 选填
                -->
            <selector-class name="classname" priority="1"></selector-class>
            <!--
                @language 必填
                -->
            <script language="java"></script>
        </method-selector>
    </method-selectors>

    <!--test定义一次测试执行,以下为各属性含义及取值
        @name:必填,test的名字,测试报告中会有体现
        @parallel:选填,是否多线程并发运行测试;可选值(false | methods | tests | classes | instances),默认 "false"
        @thread-count:选填,当为并发执行时的线程池数量,默认为"5"
        @time-out:选填,为具体执行单元设定一个超时时间,具体参照parallel的执行单元设置;单位为毫秒
        @enabled:选填,设置当前test是否生效,可选值(true | false),默认"true"
        @skipfailedinvocationcounts:选填,是否跳过失败的调用,可选值(true | false),默认"false"
        @preserve-order:选填,顺序执行开关,可选值(true | false) "true"
        @group-by-instances:选填,是否按实例分组,可选值(true | false) "false"
        @allow-return-values:选填,是否允许返回函数值,可选值(true | false),默认"false"
        -->
    <test name="testename"  parallel="false" thread-count="5" 
          time-out="10000" enabled="true" skipfailedinvocationcounts="true" preserve-order="true"
          allow-return-values="true">
        <!--局部参数,@name和@value必填,分别为参数名和参数值,如果参数名与全局参数一致,则覆盖全局参数取值-->
        <parameter name="par1" value="value1"></parameter>
        <parameter name="par2" value="value2"></parameter>
        <!--搭配class使用,执行class内指定组-->
        <groups>
            <!--定义执行组名,在run中使用
                @name 必填,组中组的名称
            -->
            <define name="xxx">
                <!--定义包含的测试组,测试方法属于哪个测试组在测试代码注释中定义。
                    @name 必填,需要包含进组中组的组名
                    @description 选填,关于组的描述
                    @invocation-numbers 选填,执行次序或者执行次数——TODO
                    -->
                <include name="" description="" invocation-numbers=""/>
                <include name="" description="" invocation-numbers=""/>
            </define>
            <!--运行组中组的配置-->
            <run>
                <!--执行指定的组中组,@name必填,与define name一致-->
                <include name=""/>
                <!--排除指定的组中组,@name必填,与define name一致-->
                <exclude name=""/>
            </run>
            <!--组中组的依赖配置-->
            <dependencies>
                <!--配置依赖
                    @name 必填,需要依赖其他组的组名,define中设置
                    @depends-on 必填,被依赖的组名,define中设置,可以有多个,用空格隔开
                    -->
                <group name="" depends-on=""></group>
                <group name="" depends-on=""></group>
            </dependencies>
        </groups>
        <!--配置要执行的类,是多个class的集合-->
        <classes>
            <!--局部参数,@name和@value必填,分别为参数名和参数值,如果参数名与全局参数和父标签的局部参数一致,则覆盖全局参数和父标签的局部参数取值-->
            <parameter name="par1" value="value1"></parameter>
            <parameter name="par2" value="value2"></parameter>
            <!--多个methods的集合,@name 必填,对应class的名称,如com.example.autotest.testcase-->
            <class name="classname">
                <!--要执行的方法,如为空,则执行整个class内包含的全部方法-->
                <methods>
                    <!--局部参数,@name和@value必填,分别为参数名和参数值,如果参数名与全局参数和父标签的局部参数一致,则覆盖全局参数和父标签的局部参数取值-->
                    <parameter name="par3" value="value3"></parameter>
                    <!--类内要执行的测试方法名,在测试代码注释中配置,如设置inclde,则只执行该方法,其他跳过
                        @name 必填,执行方法名
                        @description 选填,方法描述
                        @invocation-number 选填,宣发执行顺序或执行次数——TODO
                        -->
                    <include name="" description="" invocation-numbers=""></include>
                    <!--除了该方法外,类内其他方法都执行,@name 必填,不执行的方法名-->
                    <exclude name=""></exclude>
                </methods>
                <methods></methods>
            </class>
        </classes>
        <!--可以执行指定包下面所有类,是多个package的汇聚-->
        <packages>
            <!--配置要执行的包,@name 必填,要执行的package名-->
            <package name="">
                <!--包内要执行的测试方法名,在测试代码注释中配置,如设置inclde,则只执行该方法,其他跳过
                    @name 必填,执行方法名
                    @description 选填,方法描述
                    @invocation-number 选填,宣发执行顺序或执行次数——TODO
                    -->
                <include name="" description="" invocation-numbers=""></include>
                <!--除了该方法外,包内其他方法都执行,name 必填,不执行的方法名-->
                <exclude name=""></exclude>
            </package>
        </packages>
    </test>
    <!--设置监听的类名,可设置多个,class-name 必填,类名-->
    <listeners>
        <listener class-name="MyListener"/>
        <listener class-name="xxxxx"/>
    </listeners>
</suite>

TestNG 实用教程【5、使用 TestNG 进行单元测试】

1、 模拟实际工程,新建一个简易的 springboot 项目



2、在 pom 文件中,加入服务所需依赖(只是演示如何进行单元测试,依赖并不需要向开发那么多,真实项目 POM 文件中依赖是非常多的)

<dependencies>
       <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-web</artifactId>
       </dependency>
       <dependency>
           <groupId>org.mybatis.spring.boot</groupId>
           <artifactId>mybatis-spring-boot-starter</artifactId>
           <version>2.2.2</version>
       </dependency>

       <dependency>
           <groupId>mysql</groupId>
           <artifactId>mysql-connector-java</artifactId>
           <scope>runtime</scope>
       </dependency>
       <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-test</artifactId>
           <scope>test</scope>
       </dependency>
       <dependency>
           <groupId>org.projectlombok</groupId>
           <artifactId>lombok</artifactId>
           <optional>true</optional>
       </dependency>

       <dependency>
           <groupId>org.testng</groupId>
           <artifactId>testng</artifactId>
           <version>6.14.3</version>
           <scope>test</scope>
       </dependency>

       <dependency>
           <groupId>org.apache.maven.surefire</groupId>
           <artifactId>surefire-testng</artifactId>
           <version>3.0.0-M6</version>
       </dependency>
   </dependencies>

3、在 application.properties 中添加数据库信息

server.port=8001
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/springboottest 
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.max-idle=10
spring.datasource.max-wait=10000
spring.datasource.min-idle=5
spring.datasource.initial-size=5

4、编写服务端项目代码

我的目录结构:

定义一个简单接口,通过 id 查询数据库表 test_table 中的信息,并返回给前端

package com.example.demo.controller;

import com.example.demo.dto.TestTable;
import com.example.demo.mapper.TestTableMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping(value = "/test")
public class TestTableController {

    @Autowired
    private TestTableMapper testTableMapper;

   //根据id查询数据表user_table对应的数据信息
    @GetMapping(value = "/getValues/{id}")
    public TestTable getValues(@PathVariable(value = "id") int id){
        TestTable testTable = testTableMapper.getById(id);
        return testTable;
    }
}

定义一个数据库操作对象,里面有两个方法,一个是能过 id 查询,一个是能过 id 和 name 进行修改数据库记录

package com.example.demo.mapper;

import com.example.demo.dto.TestTable;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;

@Mapper
public interface  TestTableMapper {

    @Select("select * from user_table where id = #{id}")
    TestTable getById(int id) ;

    @Update("update user_table set name = #{name} where id = #{id}")
    int updateName(int id,String name) ;
}

数据传输对象

package com.example.demo.dto;

import lombok.Data;

@Data
public class TestTable {

    private int id;
    private String name;
    private String msg;

}

数据库表也非常简单

id name msg
1 张三 我是张三
2 李四 我是李四

启动 springboot 项目

编写的接口可被正常调用,并且返回了数据库 id=1 的信息

对 TestTableController 和 TestTableMapper 类进行单元测试

可以直接使用@SpringBootTest ,使用 Test 结尾的注解,具有加载 applicationContext 的能力

在 test 目录下,新建 TestTableController 测试类-TestTableControllerTestCase
验证 TestTableController 的 getValues 方法,返回的数据是否为我们预期的数据

package com.example.demo.controller;

import com.example.demo.dto.TestTable;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
import org.testng.annotations.Test;

import static org.testng.Assert.assertEquals;

@SpringBootTest
public class TestTableControllerTestCase extends AbstractTestNGSpringContextTests {

    @Autowired
    private TestTableController testTableController;

    @Test
    public void test1 () throws Exception {
        TestTable testTable = testTableController.getValues(1);
        assertEquals(testTable.getName(),"张三");
        assertEquals(testTable.getMsg(),"我是张三");
    }

}

在 test 目录下,新建 TestTableMapper 测试类-TestTableMapperTestCase
验证 TestTableMapper 的 update 方法,是否能成功修改数据库
执行了 update 方法,然后使用了 TestTableController 的 getValues 方法获取了返回值,进行了比较

package com.example.demo.controller;

import com.example.demo.dto.TestTable;
import com.example.demo.mapper.TestTableMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
import org.testng.annotations.Test;

import static org.testng.Assert.assertEquals;

@SpringBootTest
public class TestTableMapperTestCase extends AbstractTestNGSpringContextTests {

    @Autowired
    private TestTableController testTableController;

    @Autowired
    private TestTableMapper testTableMapper;


    @Test
    public void test1 () throws Exception {

        testTableMapper.updateName(2,"赵五");
        TestTable testTable = testTableController.getValues(2);
        assertEquals(testTable.getName(),"赵五");
        assertEquals(testTable.getMsg(),"我是李四");
    }

}

我们也可以生成一个 xml 文件,对测试用例进行管理

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
<suite name="All Test Suite">
    <test verbose="2" preserve-order="true" name="testcaseone">
        <classes >
            <class name="com.example.demo.controller.TestTableMapperTestCase"></class>
            <class name="com.example.demo.controller.TestTableControllerTestCase"></class>
        </classes>
    </test>
</suite>

集成测试

我们写好的单元测试脚本也可以集成在流程中,在指定的环节进行执行
注:这里强制指定了 testng ,如果不进行指定,会自动使用 Junit 执行 testng 测试脚本

在 pom 文件中添加

<plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>3.0.0-M6</version>
                <configuration>
                    <suiteXmlFiles>
                        <suiteXmlFile>src/test/testng.xml</suiteXmlFile>
                    </suiteXmlFiles>
                    <!--mvn test命令输出默认报告的文件目录 -->
                    <reportsDirectory>${basedir}/test-out</reportsDirectory>
                </configuration>
<!--  这里强制指定了testng ,如果不进行指定有时候会自动指向Junit执行测试脚本 -->
                <dependencies>
                    <dependency>
                        <groupId>org.apache.maven.surefire</groupId>
                        <artifactId>surefire-testng</artifactId>
                        <version>3.0.0-M6</version>
                    </dependency>
                </dependencies>
            </plugin>

这样我们可以直接使用 mvn 命令一键执行,我们单元测试脚本

mvn clean test

同时 idea 也提供了一个很有意思的一个运行方式:run with coverage


可以查看到我们单元测试脚本,代码覆盖率

共收到 4 条回复 时间 点赞

有些监听器是不是和 beforexxx 这种注解功能一样了?

时机上接近,但场景上不一样。

监听器更多是类似 AOP ,方便各种框架获取测试运行时相关数据以及有需要时改变运行结果,一般只有框架开发人员用。
而 beforexx 这些注解更多是用于测试运行时减少代码重复用,拿不到太多测试运行时数据(比如上一个用例的执行结果、用例名等),一般是写自动化测试的人员用。

请教一下,我有个 case 做了参数化,提供了三组数据,testNG.xml 如下图,设置了并发。但是实际跑的时候并没有同时启动三个浏览器,而是按照顺序来的,请问这是为什么呢?

lyyyyyyy 回复

我理解的是,这块应该和你调用 driver 程序编写有关。可以贴一下

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册