項目地址:https://github.com/JunManYuan...,我以爲出去測試框架部分的內容之外,有兩個地方值得借鑑。開發過程當中遇到的問題和寫過的BUG
都在測開筆記裏面了,有興趣能夠一讀。java
號外:這個倉庫裏面都是一些開源測試框架和測試平臺,你們有GitHub帳號的請不要吝嗇星星。
多線程處理用例參數和執行用例場景下,線程池的引入。這個首先解決了多用例運行的耗時太多的問題,其次也解決了每次處理任務新建線程對於性能的消耗。git
具體的方案就是新建一個全局的線程池,而後把全部多線程任務包裝成一個線程對象,經過將任務丟到線程池中,而後經過CountDownLatch
這個類實現等待執行結束,而後進行下一步操做。具體可參考:- CountDownLatch類在性能測試中應用。github
核心代碼以下:數據庫
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); } } }