一、项目结构
maven引入jar包
<dependencies>
<!--Mysql链接-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.41</version>
</dependency>
<!--testNG-->
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>7.0.0-beta1</version>
</dependency>
<!--lombok java bean-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.10</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.62</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.5</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.10</version>
</dependency>
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20170516</version>
</dependency>
<!--测试报告-->
<dependency>
<groupId>com.relevantcodes</groupId>
<artifactId>extentreports</artifactId>
<version>2.41.1</version>
</dependency>
<dependency>
<groupId>com.vimalselvam</groupId>
<artifactId>testng-extentsreport</artifactId>
<version>1.3.1</version>
</dependency>
<dependency>
<groupId>com.aventstack</groupId>
<artifactId>extentreports</artifactId>
<version>3.0.6</version>
</dependency>
</dependencies>
二、编写javabean
(1)User.java
package org.example.model;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
public class User {
@ApiModelProperty(value = "编号")
private int id;
@ApiModelProperty(value = "用户名")
private String username;
@ApiModelProperty(value = "密码")
private String password;
@ApiModelProperty(value = "年龄")
private int age;
@ApiModelProperty(value = "性别")
private int sex;
@ApiModelProperty(value = "是否允许")
private boolean permission;
@ApiModelProperty(value = "是否删除")
private boolean isDelete;
}
(2)addUserCase.java
package org.example.model;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
public class AddUserCase {
@ApiModelProperty(value = "编号")
private int id;
@ApiModelProperty(value = "用户名")
private String username;
@ApiModelProperty(value = "密码")
private String password;
@ApiModelProperty(value = "年龄")
private int age;
@ApiModelProperty(value = "性别")
private int sex;
@ApiModelProperty(value = "是否允许")
private boolean permission;
@ApiModelProperty(value = "是否删除")
private boolean isDelete;
@ApiModelProperty(value = "预期结果")
private String expected;
}
(3)GetUserInfoCase.java
package org.example.model;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
public class GetUserInfoCase {
@ApiModelProperty(value = "编号")
private int id;
@ApiModelProperty(value = "用户id")
private int userId;
@ApiModelProperty(value = "预期结果")
private String expected;
}
(4)GetUserListCase.java
package org.example.model;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
public class GetUserListCase {
@ApiModelProperty(value = "编号")
private int id;
@ApiModelProperty(value = "用户名")
private String username;
@ApiModelProperty(value = "年龄")
private int age;
@ApiModelProperty(value = "性别")
private int sex;
@ApiModelProperty(value = "预期结果")
private String expected;
}
(5)UpdateUserInfoCase.java
package org.example.model;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
public class UpdateUserInfoCase {
@ApiModelProperty(value = "编号")
private int id;
@ApiModelProperty(value = "用户id")
private int userId;
@ApiModelProperty(value = "用户名")
private String username;
}
(6)LoginCase.java
package org.example.model;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
public class LoginCase {
@ApiModelProperty(value = "编号")
private int id;
@ApiModelProperty(value = "用户名")
private String username;
@ApiModelProperty(value = "密码")
private String password;
@ApiModelProperty(value = "预期结果")
private String expected;
}
(7)InterfaceName.java 用来管理接口传入的地址
package org.example.model;
public enum InterfaceName {
ADDUSER,
GETUSERINFO,
GETUSERLIST,
LOGIN,
UPDATEUSERINFO
}
三、编写url管理的配置文件application.properties和读取url地址的类
(1)编写application.porperties文件
# 接口地址管理
test.url=http://127.0.0.1:8888
# 登录接口
login.uri=/user/login
# 更新用户信息接口
updateUserInfo.uri=/user/updateUserInfo
# 获取用户列表接口
getUserList.uri=/user/getUserList
# 获取用户信息接口
getUserInfo.uri=/user/getUserInfo
# 添加用户接口
addUser.uri=/user/addUser
(2)在tools包下建立ConfigFile.java用于读取url。这配合InterfaceName这个枚举类是为了避免接口地址乱传。再后面编写测试用例的时候能够体现出来。
package org.example.tools;
import org.example.model.InterfaceName;
import java.util.ResourceBundle;
public class ConfigFile {
private static ResourceBundle bundle = ResourceBundle.getBundle("application");
public static String getUrl(InterfaceName name){
String uri = null;
String testUrl;
String baseUrl = bundle.getString("test.url");
if (InterfaceName.ADDUSER == name){
uri = bundle.getString("addUser.uri");
} else if (InterfaceName.GETUSERINFO == name){
uri = bundle.getString("getUserInfo.uri");
} else if (InterfaceName.GETUSERLIST == name){
uri = bundle.getString("getUserList.uri");
} else if (InterfaceName.LOGIN == name){
uri = bundle.getString("login.uri");
} else if (InterfaceName.UPDATEUSERINFO == name){
uri = bundle.getString("updateUserInfo.uri");
}
testUrl = baseUrl+ uri;
return testUrl;
}
}
四、配置Mybatis核心配置
(1)通过jdbc.properties管理数据库链接信息
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/example?characterEncoding=utf-8
jdbc.username=root
jdbc.password=root
(2)配置Mybatis核心配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<properties resource="jdbc.properties">
<property name="jdbc.username" value="root"/>
<property name="jdbc.password" value="root"/>
</properties>
<environments default="dev">
<environment id="dev">
<transactionManager type="JDBC" />
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>
</configuration>
(3)编写工具类DataBaseUtils.java 管理连接数据库
package org.example.tools;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.Reader;
public class DataBaseUtils {
public static SqlSession getSqlSession() throws IOException {
Reader reader = Resources.getResourceAsReader("mybatis-config.xml");
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(reader);
SqlSession sqlSession = sessionFactory.openSession();
return sqlSession;
}
}
五、编写测试用例
(1)编写TestConfig.java用于管理接口地址。
package org.example.config;
import org.apache.http.client.CookieStore;
import org.apache.http.impl.client.DefaultHttpClient;
public class TestConfig {
public static String addUserUrl;
public static String getUserInfoUrl;
public static String getUserListUrl;
public static String loginUrl;
public static String updateUserInfoUrl;
public static DefaultHttpClient defaultHttpClient;
public static CookieStore cookieStore;
}
(2)编写登录的测试LoginTest.java
package org.example.cases;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.ibatis.session.SqlSession;
import org.example.config.TestConfig;
import org.example.model.InterfaceName;
import org.example.model.LoginCase;
import org.example.tools.ConfigFile;
import org.example.tools.DataBaseUtils;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;
import java.io.IOException;
import java.util.Objects;
public class LoginTest {
@BeforeTest(groups = "loginTrue", description = "测试前准备")
public void beforeTest(){
TestConfig.updateUserInfoUrl = ConfigFile.getUrl(InterfaceName.UPDATEUSERINFO);
TestConfig.addUserUrl = ConfigFile.getUrl(InterfaceName.ADDUSER);
TestConfig.getUserInfoUrl = ConfigFile.getUrl(InterfaceName.GETUSERINFO);
TestConfig.getUserListUrl = ConfigFile.getUrl(InterfaceName.GETUSERLIST);
TestConfig.loginUrl = ConfigFile.getUrl(InterfaceName.LOGIN);
TestConfig.defaultHttpClient = new DefaultHttpClient();
}
@Test(groups = "loginTrue", description = "登录成功")
public void loginTrue() throws IOException {
SqlSession sqlSession = DataBaseUtils.getSqlSession();
LoginCase loginCase = sqlSession.selectOne("loginCase","1");
System.out.println(loginCase.toString());
System.out.println(TestConfig.loginUrl);
}
@Test(groups = "loginFalse", description = "登录失败")
public void loginFalse() throws IOException {
SqlSession sqlSession = DataBaseUtils.getSqlSession();
LoginCase loginCase = sqlSession.selectOne("loginCase", "2");
if (!Objects.isNull(loginCase)){
System.out.println(loginCase.toString());
System.out.println(TestConfig.loginUrl);
}
}
}
(3)编写Mapper文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.example.model">
<select id="loginCase" resultType="org.example.model.LoginCase">
select * from logincase where id = #{id}
</select>
</mapper>
(4)在Mybatis核心配置文件Mybatis-config.xml配置mapper引入。
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<properties resource="jdbc.properties">
<property name="jdbc.username" value="root"/>
<property name="jdbc.password" value="root"/>
</properties>
<environments default="dev">
...
</environments>
<mappers>
<mapper resource="mapper/LoginCaseMapper.xml" />
<package name="org.example.model.LoginCase"/>
</mappers>
</configuration>
(5)配置testng.xml文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd" >
<suite name="用户管理系统测试套件">
<test name="用户管理系统测试用例">
<classes>
<class name="org.example.cases.LoginTest">
<methods>
<include name="loginTrue" />
<include name="loginFalse" />
</methods>
</class>
</classes>
</test>
</suite>
剩下的就不记录了,只是作为学习笔记
六、生成测试报告
(1)引入监听器,网上可以找也可以拿着这个直接用
package org.example.config;
import com.aventstack.extentreports.ExtentReports;
import com.aventstack.extentreports.ExtentTest;
import com.aventstack.extentreports.ResourceCDN;
import com.aventstack.extentreports.Status;
import com.aventstack.extentreports.model.TestAttribute;
import com.aventstack.extentreports.reporter.ExtentHtmlReporter;
import com.aventstack.extentreports.reporter.configuration.ChartLocation;
import com.aventstack.extentreports.reporter.configuration.Theme;
import org.testng.*;
import org.testng.xml.XmlSuite;
import java.io.File;
import java.util.*;
public class ExtentTestNGIReporterListener implements IReporter {
//生成的路径以及文件名
private static final String OUTPUT_FOLDER = "test-output/";
private static final String FILE_NAME = "index.html";
private ExtentReports extent;
@Override
public void generateReport(List<XmlSuite> xmlSuites, List<ISuite> suites, String outputDirectory) {
init();
boolean createSuiteNode = false;
if(suites.size()>1){
createSuiteNode=true;
}
for (ISuite suite : suites) {
Map<String, ISuiteResult> result = suite.getResults();
//如果suite里面没有任何用例,直接跳过,不在报告里生成
if(result.size()==0){
continue;
}
//统计suite下的成功、失败、跳过的总用例数
int suiteFailSize=0;
int suitePassSize=0;
int suiteSkipSize=0;
ExtentTest suiteTest=null;
//存在多个suite的情况下,在报告中将同一个一个suite的测试结果归为一类,创建一级节点。
if(createSuiteNode){
suiteTest = extent.createTest(suite.getName()).assignCategory(suite.getName());
}
boolean createSuiteResultNode = false;
if(result.size()>1){
createSuiteResultNode=true;
}
for (ISuiteResult r : result.values()) {
ExtentTest resultNode;
ITestContext context = r.getTestContext();
if(createSuiteResultNode){
//没有创建suite的情况下,将在SuiteResult的创建为一级节点,否则创建为suite的一个子节点。
if( null == suiteTest){
resultNode = extent.createTest(r.getTestContext().getName());
}else{
resultNode = suiteTest.createNode(r.getTestContext().getName());
}
}else{
resultNode = suiteTest;
}
if(resultNode != null){
resultNode.getModel().setName(suite.getName()+" : "+r.getTestContext().getName());
if(resultNode.getModel().hasCategory()){
resultNode.assignCategory(r.getTestContext().getName());
}else{
resultNode.assignCategory(suite.getName(),r.getTestContext().getName());
}
resultNode.getModel().setStartTime(r.getTestContext().getStartDate());
resultNode.getModel().setEndTime(r.getTestContext().getEndDate());
//统计SuiteResult下的数据
int passSize = r.getTestContext().getPassedTests().size();
int failSize = r.getTestContext().getFailedTests().size();
int skipSize = r.getTestContext().getSkippedTests().size();
suitePassSize += passSize;
suiteFailSize += failSize;
suiteSkipSize += skipSize;
if(failSize>0){
resultNode.getModel().setStatus(Status.FAIL);
}
resultNode.getModel().setDescription(String.format("Pass: %s ; Fail: %s ; Skip: %s ;",passSize,failSize,skipSize));
}
buildTestNodes(resultNode,context.getFailedTests(), Status.FAIL);
buildTestNodes(resultNode,context.getSkippedTests(), Status.SKIP);
buildTestNodes(resultNode,context.getPassedTests(), Status.PASS);
}
if(suiteTest!= null){
suiteTest.getModel().setDescription(String.format("Pass: %s ; Fail: %s ; Skip: %s ;",suitePassSize,suiteFailSize,suiteSkipSize));
if(suiteFailSize>0){
suiteTest.getModel().setStatus(Status.FAIL);
}
}
}
// for (String s : Reporter.getOutput()) {
// extent.setTestRunnerOutput(s);
// }
extent.flush();
}
private void init() {
//文件夹不存在的话进行创建
File reportDir= new File(OUTPUT_FOLDER);
if(!reportDir.exists()&& !reportDir .isDirectory()){
reportDir.mkdir();
}
ExtentHtmlReporter htmlReporter = new ExtentHtmlReporter(OUTPUT_FOLDER + FILE_NAME);
// 设置静态文件的DNS
//怎么样解决cdn.rawgit.com访问不了的情况
htmlReporter.config().setResourceCDN(ResourceCDN.EXTENTREPORTS);
htmlReporter.config().setDocumentTitle("api自动化测试报告");
htmlReporter.config().setReportName("api自动化测试报告");
htmlReporter.config().setChartVisibilityOnOpen(true);
htmlReporter.config().setTestViewChartLocation(ChartLocation.TOP);
htmlReporter.config().setTheme(Theme.STANDARD);
htmlReporter.config().setCSS(".node.level-1 ul{ display:none;} .node.level-1.active ul{display:block;}");
extent = new ExtentReports();
extent.attachReporter(htmlReporter);
extent.setReportUsesManualConfiguration(true);
}
private void buildTestNodes(ExtentTest extenttest, IResultMap tests, Status status) {
//存在父节点时,获取父节点的标签
String[] categories=new String[0];
if(extenttest != null ){
List<TestAttribute> categoryList = extenttest.getModel().getCategoryContext().getAll();
categories = new String[categoryList.size()];
for(int index=0;index<categoryList.size();index++){
categories[index] = categoryList.get(index).getName();
}
}
ExtentTest test;
if (tests.size() > 0) {
//调整用例排序,按时间排序
Set<ITestResult> treeSet = new TreeSet<ITestResult>(new Comparator<ITestResult>() {
@Override
public int compare(ITestResult o1, ITestResult o2) {
return o1.getStartMillis()<o2.getStartMillis()?-1:1;
}
});
treeSet.addAll(tests.getAllResults());
for (ITestResult result : treeSet) {
Object[] parameters = result.getParameters();
String name="";
//如果有参数,则使用参数的toString组合代替报告中的name
for(Object param:parameters){
name+=param.toString();
}
if(name.length()>0){
if(name.length()>50){
name= name.substring(0,49)+"...";
}
}else{
name = result.getMethod().getMethodName();
}
if(extenttest==null){
test = extent.createTest(name);
}else{
//作为子节点进行创建时,设置同父节点的标签一致,便于报告检索。
test = extenttest.createNode(name).assignCategory(categories);
}
//test.getModel().setDescription(description.toString());
//test = extent.createTest(result.getMethod().getMethodName());
for (String group : result.getMethod().getGroups())
test.assignCategory(group);
List<String> outputList = Reporter.getOutput(result);
for(String output:outputList){
//将用例的log输出报告中
test.debug(output);
}
if (result.getThrowable() != null) {
test.log(status, result.getThrowable());
}
else {
test.log(status, "Test " + status.toString().toLowerCase() + "ed");
}
test.getModel().setStartTime(getTime(result.getStartMillis()));
test.getModel().setEndTime(getTime(result.getEndMillis()));
}
}
}
private Date getTime(long millis) {
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(millis);
return calendar.getTime();
}
}
(2)testng.xml中配置监听器
<listeners>
<listener class-name="org.example.config.ExtentTestNGIReporterListener" />
</listeners>
七、执行结果
(1)运行结果:
(2)测试报告: