开源测试服务
时间:2022-07-23
本文章向大家介绍开源测试服务,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。
终于测试完成了,也上线了,虽然有些曲折,一期目标基本达成。
项目地址:https://github.com/JunManYuanLong/fun-svr,我觉得出去测试框架部分的内容以外,有两个地方值得借鉴。开发过程中遇到的问题和写过的BUG
都在测开笔记里面了,有兴趣可以一读。
号外:这个仓库里面都是一些开源测试框架和测试平台,大家有GitHub账号的请不要吝啬星星。
多线程
多线程处理用例参数和执行用例场景下,线程池的引入。这个首先解决了多用例运行的耗时太多的问题,其次也解决了每次处理任务新建线程对于性能的消耗。
具体的方案就是新建一个全局的线程池,然后把所有多线程任务包装成一个线程对象,通过将任务丢到线程池中,然后通过CountDownLatch
这个类实现等待执行结束,然后进行下一步操作。具体可参考:- CountDownLatch类在性能测试中应用。
核心代码如下:
线程池
package com.okay.family.common.threadpool;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* 自定义线程池,用例批量运行用例,非并发测试线程池
*/
public class OkayThreadPool {
private static ThreadPoolExecutor executor = createPool();
public static void addSyncWork(Runnable runnable) {
executor.execute(runnable);
}
private static ThreadPoolExecutor createPool() {
return new ThreadPoolExecutor(16, 100, 10, TimeUnit.SECONDS, new LinkedBlockingQueue<>(1000));
}
}
多线程类:
package com.okay.family.common.threadpool;
import com.okay.family.common.basedata.OkayConstant;
import com.okay.family.common.bean.testcase.CaseRunRecord;
import com.okay.family.common.bean.testcase.request.CaseDataBean;
import com.okay.family.common.enums.CaseAvailableStatus;
import com.okay.family.common.enums.RunResult;
import com.okay.family.utils.RunCaseUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.CountDownLatch;
public class CaseRunThread implements Runnable {
private static Logger logger = LoggerFactory.getLogger(CaseRunThread.class);
int envId;
CaseDataBean bean;
CaseRunRecord record;
CountDownLatch countDownLatch;
public CaseRunRecord getRecord() {
return record;
}
private CaseRunThread() {
}
public CaseRunThread(CaseDataBean bean, CountDownLatch countDownLatch, int runId, int envId) {
this.bean = bean;
this.envId = envId;
this.countDownLatch = countDownLatch;
this.record = new CaseRunRecord();
record.setRunId(runId);
record.setUid(bean.getUid());
record.setParams(bean.getParams());
record.setCaseId(bean.getId());
record.setMark(OkayConstant.RUN_MARK.getAndIncrement());
bean.getHeaders().put(OkayConstant.MARK_HEADER, record.getMark());
record.setHeaders(bean.getHeaders());
}
@Override
public void run() {
try {
if (bean.getAvailable() == RunResult.USER_ERROR.getCode()) {
record.fail(RunResult.USER_ERROR, bean);
} else if (bean.getEnvId() != envId || bean.getAvailable() != CaseAvailableStatus.OK.getCode()) {
record.fail(RunResult.UNRUN, bean);
} else {
RunCaseUtil.run(bean, record);
}
} catch (Exception e) {
logger.warn("用例运行出错,ID:" + bean.getId(), e);
record.fail(RunResult.UNRUN, bean);
} finally {
countDownLatch.countDown();
}
}
}
对于用户锁的实现
其中包括线程同步锁和分布式锁。之所以采用两个,主要是因为竞争中拿不到锁的时候,不会像业务开发那样直接丢出来拿锁失败的业务,而是需要等待其他线程安全对用户的验证之后,再取出最新的用户凭证。这里面涉及到的东西比较复杂,中间因为逻辑问题我也写了好几个BUG。
这里涉及的一些多线程编程的内容,还有在多用例执行的过程中我用到ConcurrentHashMap
作为缓存,第一是为了减少对数据库的读写。第二是为了防止用例中大量引用错误的用户导致执行时间变长。
核心代码如下:
/**
* 获取用户登录凭据,map缓存
*
* @param id
* @param map
* @return
*/
@Override
@Transactional(isolation = Isolation.REPEATABLE_READ)
public String getCertificate(int id, ConcurrentHashMap<Integer, String> map) {
if (map.containsKey(id)) return map.get(id);
Object o = UserLock.get(id);
synchronized (o) {
if (map.containsKey(id)) return map.get(id);
logger.warn("非缓存读取用户数据{}", id);
TestUserCheckBean user = testUserMapper.findUser(id);
if (user == null) UserStatusException.fail("用户不存在,ID:" + id);
String create_time = user.getCreate_time();
long create = Time.getTimestamp(create_time);
long now = Time.getTimeStamp();
if (now - create < OkayConstant.CERTIFICATE_TIMEOUT && user.getStatus() == UserState.OK.getCode()) {
map.put(id, user.getCertificate());
return user.getCertificate();
}
boolean b = UserUtil.checkUserLoginStatus(user);
logger.info("环境:{},用户:{},身份:{},登录状态验证:{}", user.getEnvId(), user.getId(), user.getRoleId(), b);
if (!b) {
updateUserStatus(user);
if (user.getStatus() != UserState.OK.getCode()) {
map.put(id, OkayConstant.EMPTY);
UserStatusException.fail("用户不可用,ID:" + id);
}
} else {
testUserMapper.updateUserStatus(user);
}
map.put(id, user.getCertificate());
return user.getCertificate();
}
}
/**
* 更新用户登录状态,全局锁+分布式锁
*
* @param bean
* @return
*/
@Override
@Transactional(isolation = Isolation.REPEATABLE_READ)
public int updateUserStatus(TestUserCheckBean bean) {
int userLock = NodeLock.getUserLock(bean.getId());
int lock = commonService.lock(userLock);
if (lock == 0) {
logger.info("分布式锁竞争失败,ID:{}", bean.getId());
int i = 0;
while (true) {
SourceCode.sleep(OkayConstant.WAIT_INTERVAL);
TestUserCheckBean user = testUserMapper.findUser(bean.getId());
String create_time = user.getCreate_time();
long create = Time.getTimestamp(create_time);
long now = Time.getTimeStamp();
if (now - create < OkayConstant.CERTIFICATE_TIMEOUT && user.getStatus() == UserState.OK.getCode()) {
bean.copyFrom(user);
return testUserMapper.updateUserStatus(bean);
}
if (i++ > OkayConstant.WAIT_MAX_TIME) {
UserStatusException.fail("获取分布式锁超时,无法更新用户凭据:id:" + bean.getId());
}
}
} else {
logger.info("分布式锁竞争成功,ID:{}", bean.getId());
try {
TestUserCheckBean user = testUserMapper.findUser(bean.getId());
String create_time = user.getCreate_time();
long create = Time.getTimestamp(create_time);
long now = Time.getTimeStamp();
if (bean.same(user) && StringUtils.isNotBlank(user.getCertificate())) {
if (now - create < OkayConstant.CERTIFICATE_TIMEOUT && user.getStatus() == UserState.OK.getCode()) {
bean.copyFrom(user);
return testUserMapper.updateUserStatus(bean);
}
if (UserUtil.checkUserLoginStatus(user)) bean.copyFrom(user);
}
UserUtil.updateUserStatus(bean);
return testUserMapper.updateUserStatus(bean);
} catch (Exception e) {
logger.error("用户验证失败!ID:{}", bean.getId(), e);
bean.setStatus(UserState.CANNOT.getCode());
return testUserMapper.updateUserStatus(bean);
} finally {
commonService.unlock(userLock);
}
}
}
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- Centos7学习之添加用户和用户组的方法
- Linux静态库与动态库实例详解
- Linux字符终端如何用鼠标移动一个红色矩形详解
- Linux中出现“No space left on device”错误的排查与解决方法
- 浏览器是如何调度进程和线程的?
- Linux shell利用sed如何批量更改文件名详解
- linux下通过xinetd服务管理 rsync 实现开机自启动
- linux实现自动删除最旧的几个文件详解
- 基于Ubuntu 16.04设置固定IP的方法教程
- CentOS添加和删除用户以及用户组的方法
- Linux下使用inode删除指定文件方法示例
- Apache虚拟目录配置及vue-cli反向代理的设置方法
- linux socket通讯获取本地的源端口号的实现方法
- 初识centos7与centos6的区别整理(内核、命令等)
- Apache下通过shell脚本提交网站404死链的方法