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 注解
这些是用于控制方法流的程序或业务逻辑。它们在 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);
}
}
@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>
常用属性说明
<?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>
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 的信息
可以直接使用@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
可以查看到我们单元测试脚本,代码覆盖率