審核平臺(tái)接入50+業(yè)務(wù),提供在線審核及離線質(zhì)檢、新人培訓(xùn)等核心能力,同時(shí)提供數(shù)據(jù)報(bào)表、資源追蹤、知識(shí)庫(kù)等工具。隨著平臺(tái)的飛速發(fā)展,越來(lái)越多的新業(yè)務(wù)正在或即將接入審核平臺(tái),日均頁(yè)面瀏覽量為百萬(wàn)級(jí)別。如今審核平臺(tái)已是公司內(nèi)容生產(chǎn)鏈路上的關(guān)鍵一環(huán),是保障內(nèi)容安全的重要防線,因此穩(wěn)定性至關(guān)重要。
過(guò)去一年我們?cè)鴮?duì)前端項(xiàng)目進(jìn)行框架升級(jí),考慮風(fēng)險(xiǎn)與成本最小化,選擇了漸進(jìn)式升級(jí),利用微前端實(shí)現(xiàn)Vue2和Vue3共存,新接業(yè)務(wù)在Vue3倉(cāng)庫(kù)中開(kāi)發(fā)。經(jīng)過(guò)一年的迭代,Vue3項(xiàng)目趨于穩(wěn)定,沉淀了大部分通用能力。為了降低多倉(cāng)庫(kù)維護(hù)心智,同時(shí)解決核心模塊的技術(shù)債務(wù),考慮將剩余活躍代碼進(jìn)行重構(gòu)并遷移至新倉(cāng)庫(kù)。
參考前端埋點(diǎn)報(bào)表,選擇老倉(cāng)庫(kù)中頁(yè)面維度訪問(wèn)量最高的路由,對(duì)線上使用情況進(jìn)行摸排。日常業(yè)務(wù)現(xiàn)狀是點(diǎn)直融合,直播業(yè)務(wù)配置化接入需求較多,因?yàn)闃I(yè)務(wù)形態(tài)的差異,定制需求多,現(xiàn)有配置能力無(wú)法滿足,需擴(kuò)充。開(kāi)發(fā)現(xiàn)狀是通用配置化代碼改動(dòng)頻繁,邏輯復(fù)雜,開(kāi)發(fā)門(mén)檻較高,影響范圍大,牽一發(fā)而動(dòng)全身。因此選擇配置化詳情頁(yè)作為優(yōu)先重構(gòu)并遷移的對(duì)象。
配置化詳情頁(yè)采用的是業(yè)務(wù)定制化的低代碼方案,包含schema渲染器和任務(wù)流兩部分。當(dāng)前已沉淀近百份json schema,托管在內(nèi)部其他低代碼平臺(tái)上。頁(yè)面覆蓋40多個(gè)業(yè)務(wù),占據(jù)平臺(tái)約20%訪問(wèn)量和35%獨(dú)立訪客。
圖片
圖片
如果將頁(yè)面看做一個(gè)黑盒子,依據(jù)唯一標(biāo)識(shí)(路由path和query等)從node服務(wù)、平臺(tái)服務(wù)以及外部業(yè)務(wù)方服務(wù)獲取數(shù)據(jù),基于頁(yè)面內(nèi)部規(guī)則渲染頁(yè)面。審核員瀏覽并進(jìn)行通過(guò)、駁回等操作,提交后將對(duì)視頻、彈幕等業(yè)務(wù)資源產(chǎn)生影響。
圖片
schema渲染器基于json schema和接口數(shù)據(jù),在平臺(tái)內(nèi)生成路由信息與頁(yè)面內(nèi)容,負(fù)責(zé)各種模式的頁(yè)面分發(fā)、物料分發(fā),并提供敏感詞、快照、洗數(shù)等通用平臺(tái)能力。
代碼現(xiàn)狀是數(shù)據(jù)獲取、提交操作和頁(yè)面復(fù)雜邏輯分散在vue文件和store中,業(yè)務(wù)邏輯和UI框架耦合嚴(yán)重,不利于集成自動(dòng)化測(cè)試和框架升級(jí)。待辦、任務(wù)、資源等邊界劃分不清晰,平鋪在“巨石store“中,維護(hù)成本極高且代碼改動(dòng)風(fēng)險(xiǎn)大。渲染器和任務(wù)流邏輯不夠內(nèi)聚,耦合嚴(yán)重,無(wú)法做到關(guān)注點(diǎn)分離。因此需要尋找一種合適的架構(gòu)進(jìn)行重構(gòu),減弱業(yè)務(wù)邏輯對(duì)UI框架的依賴,增強(qiáng)可測(cè)試性。
整潔架構(gòu)由Robert C. Martin在2012年提出,核心思想是將軟件系統(tǒng)拆分為獨(dú)立的層次,以實(shí)現(xiàn)高內(nèi)聚、低耦合、可測(cè)試和可維護(hù)。
圖片
一共分為四個(gè)層級(jí),環(huán)與環(huán)之間,存在一個(gè)依賴關(guān)系原則:源代碼中的依賴關(guān)系,必須只指向同心圓的內(nèi)層,即由低層機(jī)制指向高級(jí)策略。
優(yōu)點(diǎn)是可以在沒(méi)有UI、數(shù)據(jù)庫(kù)、web服務(wù)器或其他外部基礎(chǔ)設(shè)施的情況下測(cè)試業(yè)務(wù)邏輯;降低對(duì)UI框架的依賴,比如跨端開(kāi)發(fā)時(shí),業(yè)務(wù)邏輯可以復(fù)用,只需要做UI層的適配。相應(yīng)的,缺點(diǎn)也很明顯,過(guò)于復(fù)雜,數(shù)據(jù)需要經(jīng)過(guò)多層處理。學(xué)習(xí)成本較高,容易過(guò)度設(shè)計(jì),增加復(fù)雜性,靈活性較低。適用于大型復(fù)雜項(xiàng)目,對(duì)于需要長(zhǎng)期維護(hù)和持續(xù)開(kāi)發(fā)的項(xiàng)目,清晰層次結(jié)構(gòu)和明確依賴關(guān)系有助于減少代碼腐化,更容易適應(yīng)需求變化。針對(duì)我們選擇的模塊,審核前端配置化頁(yè)面,比較適合。
圖片
圖片
圖片
主要可拆分成待辦、任務(wù)、資源等實(shí)體。待辦實(shí)體,主要提供待辦的基礎(chǔ)信息、獲取配置等屬性和方法。任務(wù)實(shí)體提供任務(wù)的狀態(tài)、任務(wù)耗時(shí)、調(diào)度配置、任務(wù)數(shù)據(jù),計(jì)時(shí)和拉取任務(wù)流程等。資源實(shí)體提供資源的詳情數(shù)據(jù)、獲取詳情及數(shù)據(jù)清洗方法等。待辦實(shí)體包含了配置詳情頁(yè)所需的核心數(shù)據(jù),任務(wù)實(shí)體高度抽象了核心任務(wù)流。在新業(yè)務(wù)接入過(guò)程中,實(shí)體層一般不變動(dòng),通過(guò)依賴倒置劃分架構(gòu)邊界。
// entities/todo.jsexport default class Todo { todoId businessId todoConfig ... constructor() {} async getTodoConfig() { // 獲取配置 }}// entities/task.jsexport default class Task { dispatch_conf listData timeCount ... constructor() {} async getTaskDetail({ getTask, taskFormat, afterGetTask}) { // 抽象封裝核心任務(wù)流程 } // 計(jì)時(shí)邏輯 startTimer() {} clearTimers() {}}// entities/resource.jsexport default class Resource { detail dataReady constructor() {} async getResourceDetail({ getResource, resourceFormat, afterGetResource }) { // 抽象封裝資源模式核心流程 }}
用例層針對(duì)洗數(shù)、提交等復(fù)雜場(chǎng)景,通過(guò)調(diào)用實(shí)體層來(lái)實(shí)現(xiàn)特定的業(yè)務(wù)邏輯,是適配器層與實(shí)體層的中介。例如封裝了基于配置的洗數(shù)中間件,列表模式的單個(gè)和批量提交,卡片模式的單個(gè)和批量提交,快照上報(bào)、自動(dòng)化質(zhì)檢的復(fù)雜邏輯。用例層包含了系統(tǒng)的復(fù)雜業(yè)務(wù)邏輯,為單元測(cè)試提供了便利,可以獨(dú)立于UI和外部系統(tǒng)進(jìn)行測(cè)試。
// usecase/use-single-submitimport { get } from 'lodash-es' // 三方工具庫(kù)import { ANNOTATION_SINGLE_OPER_PASS_NAME } from '@/constants' // 常量import { workbenchApi } from '@/api'import { setLogData } from '@/utils/xx' // 工具函數(shù)export function getSingleSubmitParams({ data, state.xxx }) { //... 邏輯處理 return params}export function submitAuditSingle({ data, afterTaskSubmit }) { const params = ... workbenchApi.submit(params).then((res) => { if(res.code = xxx){ afterTaskSubmit() // 調(diào)用鉤子函數(shù) } })}
適配器層包含store和UI,調(diào)用用例層的代碼,主要負(fù)責(zé)將依賴UI、外部服務(wù)、設(shè)備等的數(shù)據(jù)處理為用例層可以使用的“干凈數(shù)據(jù)”。適配器層一般不包括復(fù)雜的業(yè)務(wù)邏輯,因此在框架遷移時(shí)僅需關(guān)注基本的框架差異,適合自動(dòng)化代碼轉(zhuǎn)換。
// store/todoConfigDetailimport { getTodoInfo } from '@/struct/TodoConfigDetailStruct/usecase/use-todo'import { getTaskInfo, getTask, taskDispatchListFormat } from '@/struct/TodoConfigDetailStruct/usecase/use-task'import { getSingleSubmitParams, submitAuditSingle } from '@/struct/TodoConfigDetailStruct/usecase/use-single-submit'const todo = ref({})async function init({ $route }) { const query = $route.query const todoId = +query.todo_id ... set(todo, getTodoInfo({ todoId, ... })) await get(todo).getTodoConfig() ...}function getTaskDetail() { get(task).getTaskDetail({ getTask: async ({ noSeize, drillTaskIds }) => await getTask({ todo: get(todo), noSeize, drillTaskIds }), taskFormat: async (data) => await taskDispatchListFormat({ data, schema: get(todo).schema }) afterGetTask: (res) => { ... } })}async function submit(data) { if (single) { const params = getSingleSubmitParams({ data, todo: get(todo) }) submitAuditSingle({ params, afterTaskSubmit: () => { ... } }) } else { ... }}
// Audit.vueconst todoConfigDetailStore = useTodoConfigDetailStore()const { todo, task, multipleSelection } = storeToRefs(todoConfigDetailStore)const { getTaskDetail, submit } = todoConfigDetailStore
新業(yè)務(wù)接入一般對(duì)實(shí)體層和用例層無(wú)改動(dòng),僅需適配器層增加相應(yīng)的展示物料,盡可能避免“牽一發(fā)動(dòng)全身”。新架構(gòu)會(huì)有一定的初學(xué)成本,但結(jié)合審核平臺(tái)復(fù)雜的項(xiàng)目現(xiàn)狀和持續(xù)接入新業(yè)務(wù)的節(jié)奏,長(zhǎng)遠(yuǎn)來(lái)看對(duì)于系統(tǒng)穩(wěn)定性、可測(cè)試性有一定幫助,同時(shí)降低UI框架依賴性。
基于新的架構(gòu),可分層進(jìn)行自動(dòng)化測(cè)試和自動(dòng)代碼轉(zhuǎn)換。
用例層為純函數(shù),不依賴框架、設(shè)備、三方服務(wù)等。單元測(cè)試的技術(shù)棧為jest和vue-test-utils,從審核員的基本工作模式入手,針對(duì)任務(wù)領(lǐng)取、數(shù)據(jù)清洗與展示、稿件處理三個(gè)環(huán)節(jié)完善測(cè)試用例。因?yàn)槭切吕蟼}(cāng)庫(kù)遷移,所以可以將線上環(huán)境視為基準(zhǔn)進(jìn)行用例采集。根據(jù)業(yè)務(wù)重要性、線上訪問(wèn)情況,按優(yōu)先級(jí)執(zhí)行測(cè)試。單測(cè)能有效降低回歸成本,在新業(yè)務(wù)持續(xù)接入的背景下,保障系統(tǒng)穩(wěn)定性。
在尋求升級(jí)方案的過(guò)程中,我們對(duì)比了兩款工具:Gogocode 和 vue2-to-composition-api。以下是它們的簡(jiǎn)要對(duì)比:
功能 | Gogocode | vue2-to-composition-api |
缺點(diǎn) | 默認(rèn)不支持轉(zhuǎn)換成 Vue3 setup 語(yǔ)法 | 不支持 template 轉(zhuǎn)換 |
轉(zhuǎn)換規(guī)則覆蓋 | 轉(zhuǎn)換規(guī)則列表 | 轉(zhuǎn)換效果 |
特性 | 像 jQuery 一樣修改AST | 將 Vue2 代碼轉(zhuǎn)換為 Vue 3 的 Composition API 格式 |
2. jQuery-like API 簡(jiǎn)化了 AST 修改成本 | 2. 支持在線轉(zhuǎn)換 | |
優(yōu)點(diǎn) | 1. 支持自定義插件 | 1. 支持轉(zhuǎn)換成 Vue3 setup 語(yǔ)法 |
經(jīng)過(guò)調(diào)研,gogocode 是基于 AST 封裝的庫(kù)可擴(kuò)展空間大,但是默認(rèn) gogocode-plugin-vue 不支持轉(zhuǎn)換成 Composition API
vue2-to-composition-api 倒是支持 Composition API,但是不支持 template 部分的轉(zhuǎn)換。
考慮到我們的項(xiàng)目中有許多自定義的轉(zhuǎn)換邏輯,如 UI 庫(kù)替換、store 替換等,我們最終決定使用 gogocode 作為主要工具,并結(jié)合其他手段來(lái)實(shí)現(xiàn) vue2 到 vue3 的全面升級(jí)。
升級(jí)語(yǔ)法是借助 gogocode 的 replace 方法實(shí)現(xiàn)的,通過(guò) $$$ 匹配符保留所需要的代碼塊,將 Vue2 的語(yǔ)法快速替換成 Vue3 的語(yǔ)法。
// 替換datascriptAst.replace("data() {return {$$$};}", `const $data = reactive({$$$})`);
// 替換propsscriptAst.replace("props:{$$$}", "const props = defineProps({$$$})");
// 替換生命周期scriptAst.replace("created(){$$$}", "onBeforeMount(()=>{$$$})") .replace("mounted(){$$$}", "onMounted(()=>{$$$})") .replace("async mounted(){$$$}", "onMounted(async ()=>{$$$})") .replace("beforeUnmount(){$$$}", "onBeforeUnmount(()=>{$$$})") .replace("unmounted(){$$$}", "onUnmounted(()=>{$$$})") .replace("beforeDestroy(){$$$}", "onBeforeUnmount(()=>{$$$})") .replace("destoryed(){$$$}", "onUnmounted(()=>{$$$})");
效果如下,通過(guò) replace 替換的方式適合大部分場(chǎng)景,比如 methods、filters、watch 等
圖片
進(jìn)階:處理模板中的變量、函數(shù)中的this變量
template綁定的變量可能是data,可能是props,還可能是 methods
想要替換 template 中的變量,需要先收集 data、props、methods 中的 keys
getDataKeys() { const keys = new Set(); // 只需要第一層的key,所以deep設(shè)為1 this.scriptAst.find('data() {$$$}').find('$_$:$_$', { deep: 1 }).each(node => { if (node.match[0] && node.match[0][0].node.type === 'Identifier') { keys.add(node.match[0][0].value); } }); return Array.from(keys)}getPropsKeys() { const keys = new Set(); this.scriptAst.find('props: {$$$1}', { deep: 1 }).each((node) => { if (node.match['$$$1']) { node.match['$$$1'].forEach((item) => { if (item.key && item.key.type === 'Identifier') { keys.add(item.key.name) } }) } }); return Array.from(keys)}// methods 有點(diǎn)小復(fù)雜,需要考慮異步函數(shù),普通函數(shù)和鍵值對(duì)的寫(xiě)法getMethodsKeys() { const methodsAst = this.scriptAst.find('methods:{$$$}'); const methods = methodsAst.find('$_$() {$$$1}'); const asyncmMethods = methodsAst.find('async $_$(){$$$1}'); const mapKeys = methodsAst.find('$_$:$$$1', { deep: 1 }); const methodNames = []; methods.each(node => { if (node.match[0] && node.match[0][0]) { methodNames.push(node.match[0][0].value); } }); asyncmMethods.each(node => { if (node.match[0] && node.match[0][0]) { methodNames.push(node.match[0][0].value); } }); mapKeys.each(node => { if (node.match[0] && node.match[0][0]) { methodNames.push(node.match[0][0].value); } }); return methodNames;}
收集完成后可以開(kāi)始遍歷 template 中的 attr,并替換所綁定的變量了。
handlTemplate() { // 替換attr, 例如 <div :value="value"></div> this.ast.find("<template></template>").find(`<$_$ ="$$$0" >$$$1</$_$>`).each((node) => { node.match['$$$0'].forEach(attr => { if (attr && attr.value) { this.dataKeys.some(keyName => { const reg = new RegExp(`${keyName}//b`, 'g') const macth = reg.test(attr.value.content); attr.value.content = attr.value.content.replace(reg, `$data.${keyName}`) if (macth) { return true; } }) this.methodsKeys.some(keyName => { const reg = new RegExp(`//b${keyName}//b`, 'g') const macth = reg.test(attr.value.content); attr.value.content = attr.value.content.replace(reg, `methods.${keyName}`) if (macth) { return true; } }) this.propsKeys.some(keyName => { const reg = new RegExp(`//b${keyName}//b`, 'g') const macth = reg.test(attr.value.content); attr.value.content = attr.value.content.replace(reg, `props.${keyName}`) if (macth) { return true; } }) } }) // 替換content,例如:<div>{{value}}<div> node.match['$$$1'].forEach(node => { if (node.content && node.content.value) { // 省略:與上面類(lèi)似 } }) }) }
說(shuō)到 this 替換也是一個(gè)繁瑣的問(wèn)題,this.xx, xx 可以是 data,可以是props,可以是 function,還可以是私有屬性等。
所以我們需要先把組件中的 data、props、methods、mapGetter 中的 keys 都收集一遍,然后再替換 script 中的 this 變量。
// 正則替換更方便,所以需要放在最后一步替換handlThis(code) { code = code.replace(/this/.([_$0-9a-zA-Z]+)/g, (match, $1) => { // 替換function body 中的 data引用 if (this.dataKeys.includes($1)) { return `$data.${$1}`; } // 替換function body 中的 methods調(diào)用 else if (this.methodsKeys.includes($1)) { return `methods.${$1}`; } // 替換 vm 私有屬性 else if ($1 && $1[0] === '$') { return `$vm.${$1}`; } else if (this.computedKeys.includes($1)) { return $1 } else if (this.propsKeys.includes($1)) { return `props.${$1}` } return `$vm.${$1}` }) // 替換function body 中的 動(dòng)態(tài)methods調(diào)用 code = code.replace(/this/[(.+)/]/g, (match, $1) => { return `methods[${$1}]`; }) return code}
async parseOpenDialog(payload) { const { schema, data } = payload await this[schema.method]( parseSchema, data, dialogParams, dialogType )}
按 vue3 新的寫(xiě)法,一般是展開(kāi)的
const parseOpenDialog = async (payload) => {}
但是按這個(gè)習(xí)慣來(lái)轉(zhuǎn)換,就無(wú)法做到動(dòng)態(tài)調(diào)用this.xxx,我們可以嘗試把方法都放在methods對(duì)象中,有點(diǎn)類(lèi)似 vue2
const methods = { parseOpenDialog: async (payload) => { }, xxx: async() => { }}
在替換 this 時(shí),將 this 替換成 methods 變成 methods[schema.method]()。
1 . 原來(lái) vue2 中肯很多屬性掛在組件實(shí)例上,比如 $route, $router, $emit 甚至自定義的屬性等等。下面是 vue3 中獲取組件實(shí)例的方法。
import { getCurrentInstance } from 'vue'const { proxy: $vm } = getCurrentInstance()$vm.xxx = '自定義屬性'
2 . 原來(lái)使用的 vuex,現(xiàn)在使用的是 pinia。我們需要先收集 ...mapState($_$, [$$$1]),然后使用 storeToRefs 代替
// 獲取store 使用storeToRefsif (this.storeType === 'pinia') { this.scriptAst .find("computed:{}") .before(`const {${stateNames.join(',')}} = storeToRefs(${this.getPiniaStoreName(key)})`) .replace("computed:{$$$}", "$$$")}
最終將上述轉(zhuǎn)換能力封裝成一個(gè)庫(kù),通過(guò) npm 安裝來(lái)實(shí)現(xiàn)組件批量升級(jí)。
遷移前后的E2E測(cè)試 - 視覺(jué)輔助UI自動(dòng)化測(cè)試
端到端測(cè)試是確保新舊系統(tǒng)平穩(wěn)過(guò)渡的關(guān)鍵步驟。本次遷移依舊遵循漸進(jìn)式升級(jí)的原則,新增v3路由,線上新老路由共存。共分為功能測(cè)試、UI測(cè)試、性能測(cè)試三個(gè)部分。功能測(cè)試為單元測(cè)試的補(bǔ)充,主要驗(yàn)證新老路由下核心操作路徑及提交參數(shù)的一致性,攔截請(qǐng)求避免對(duì)線上造成影響。性能由通用監(jiān)控大盤(pán)進(jìn)行保障。UI對(duì)比測(cè)試是本次的重點(diǎn)。
新老倉(cāng)庫(kù)分別基于Element UI和Element Plus,Element Plus重新設(shè)計(jì)了組件以適應(yīng)Vue3,組件尺寸體系調(diào)整為更自然的大中小選項(xiàng)。間距優(yōu)化為更通用的4px體系,主要涉及 padding 和 margin 屬性修改、 font-size 等字體和圖標(biāo)大小修改等。因此,雖然大部分組件在外觀上保持相似,視覺(jué)和布局上可能有一些差異。由于業(yè)務(wù)組件具備一定復(fù)雜性,手寫(xiě)測(cè)試用例工作量繁瑣,新舊頁(yè)面組件可能存在差異無(wú)法完全復(fù)用,方案也不具備通用性。因此考慮使用計(jì)算機(jī)視覺(jué)技術(shù)來(lái)識(shí)別和驗(yàn)證用戶界面的元素。
基于公司內(nèi)部的自動(dòng)化測(cè)試平臺(tái),測(cè)試框架為Playwright,測(cè)試語(yǔ)言選擇Python以更好的利用豐富的圖像處理庫(kù)。指定CSS選擇器,隨機(jī)選擇頁(yè)面上的元素進(jìn)行截圖和對(duì)比,設(shè)定閾值進(jìn)行判斷。圖像相似度對(duì)比可分為傳統(tǒng)的基于像素差的方法和基于圖像特征的方法。圖像特征有SIFT、ORB等特征提取方法和深度學(xué)習(xí)方法。分別選擇一種代表性的算法進(jìn)行對(duì)比和測(cè)試。
v2 | v3 | SSIM | SIFT | LPIPS |
0.656 | 0.855 | 0.909 | ||
0.916 | 0.874 | 0.961 | ||
0.658 | 0.857 | 0.881 | ||
0.877 | 0.855 | 0.926 | ||
0.551 | 0.836 | 0.947 | ||
0.929 | 0.884 | 0.942 |
實(shí)驗(yàn)表明,基于深度學(xué)習(xí)特征的相似度對(duì)比結(jié)果更接近用戶感知,針對(duì)Vue2升級(jí)Vue3 UI組件庫(kù)導(dǎo)致的間距、字體、尺寸等細(xì)微差異判斷更準(zhǔn)確。因此采用LPIPS作為對(duì)比算法。
def compare_images(url1, url2): loss_fn = lpips.LPIPS(net = 'alex') img1 = cv2.imread(url1) img2 = cv2.imread(url2) if img1 is not None and img2 is not None and img1.size > 0 and img2.size > 0: img2 = cv2.resize(img2, (img1.shape[1], img1.shape[0])) cv2.imwrite(url2, img2) combined_image = cv2.hconcat([img1, img2]) ex_img1 = lpips.im2tensor(lpips.load_image(url1)) ex_img2 = lpips.im2tensor(lpips.load_image(url2)) d = loss_fn.forward(ex_img1, ex_img2) if d is not None: cv2.putText(combined_image,'score: %.3f'%(1 - d.mean()), (20, 20), cv2.FONT_ITALIC, 0.4, (255, 0, 255)) return d, combined_image else: return None
依據(jù)業(yè)務(wù)流程,分別訪問(wèn)新老路由,對(duì)頁(yè)面指定元素進(jìn)行截圖、對(duì)比和拼接,輸出測(cè)試截圖和完整的測(cè)試報(bào)告。
圖片
圖片
采用視覺(jué)輔助UI自動(dòng)化測(cè)試,更接近用戶真實(shí)感知,大大降低測(cè)試用例復(fù)雜度,提升測(cè)試效率,無(wú)需關(guān)注繁雜的DOM元素層級(jí)。
以頁(yè)面配置的形式按待辦進(jìn)行灰度,跳轉(zhuǎn)至新路由。單元測(cè)試已集成至項(xiàng)目流水線中,MR和發(fā)布前觸發(fā)。灰度過(guò)程中手動(dòng)執(zhí)行E2E用例,以自定義環(huán)境變量的形式指定頁(yè)面路徑、元素選擇器、相似度閾值等。測(cè)試通過(guò)后修改頁(yè)面配置,引流至新路由。通過(guò)用戶故障群和頁(yè)面反饋入口響應(yīng)用戶反饋,結(jié)合前端埋點(diǎn)報(bào)表觀察線上使用情況和確定灰度策略。通過(guò)完善單元測(cè)試、E2E測(cè)試和制定合理的灰度策略,針對(duì)特定模板的遷移已順利完成,期間未收到線上故障反饋。
后續(xù)遷移策略將以老倉(cāng)庫(kù)中改動(dòng)較頻繁文件優(yōu)先入手,測(cè)試用例先行,借助自動(dòng)代碼轉(zhuǎn)換工具快速平穩(wěn)遷移,線上埋點(diǎn)數(shù)據(jù)做輔助。
活躍代碼陸續(xù)遷移,結(jié)束多倉(cāng)庫(kù)并行,減少維護(hù)心智。之前我們的常態(tài)是新老倉(cāng)庫(kù)并行,開(kāi)發(fā)一個(gè)完整的業(yè)務(wù)功能時(shí)要在ts和js,選項(xiàng)式API和setup語(yǔ)法之間頻繁切換,心智負(fù)擔(dān)較重。活躍代碼陸續(xù)遷移至vue3新倉(cāng)庫(kù),結(jié)合新的框架特性和實(shí)用工具,能夠更專注于業(yè)務(wù)邏輯本身。
圖片
核心模塊重構(gòu),“巨石store”輕量化,提高可維護(hù)性和可演進(jìn)性,分層結(jié)構(gòu)保障核心邏輯穩(wěn)定性。原本配置能力擴(kuò)充時(shí)需要在復(fù)雜的數(shù)據(jù)流中“走迷宮”,耦合嚴(yán)重,通用代碼影響范圍大,常常出現(xiàn)A業(yè)務(wù)需求上線導(dǎo)致B業(yè)務(wù)不可用的情況。利用整潔架構(gòu)進(jìn)行分層設(shè)計(jì)后,新增一種審核模式僅需在適配器層新增對(duì)應(yīng)物料和action,用例層新增用例。無(wú)需修改實(shí)體層和其他業(yè)務(wù)相關(guān)的用例、通用頁(yè)面、物料等。
圖片
圖片
業(yè)務(wù)邏輯的重新梳理,彌補(bǔ)測(cè)試用例空缺。領(lǐng)域提取是對(duì)業(yè)務(wù)邏輯的重新梳理,前端能加深業(yè)務(wù)理解。穩(wěn)定性至上的模塊很長(zhǎng)時(shí)間缺少測(cè)試用例,造成對(duì)開(kāi)發(fā)人員的經(jīng)驗(yàn)、能力依賴極大。完善核心鏈路的測(cè)試用例能有效降低回歸成本,保障系統(tǒng)穩(wěn)定性。
工具沉淀,組內(nèi)復(fù)用。自動(dòng)代碼轉(zhuǎn)換工具和基于AI能力的前端E2E測(cè)試方案為后續(xù)組內(nèi)其他項(xiàng)目的框架升級(jí)和遷移提供了便利。
在2023年開(kāi)始漸進(jìn)式升級(jí)Vue3后,我們經(jīng)歷了很長(zhǎng)一段時(shí)間的多倉(cāng)庫(kù)并行。在新業(yè)務(wù)不斷接入、開(kāi)發(fā)新成員加入的背景下,這樣的模式無(wú)疑提升了開(kāi)發(fā)門(mén)檻和維護(hù)心智。本次活躍代碼的陸續(xù)遷移結(jié)束了多倉(cāng)庫(kù)并行的現(xiàn)狀,同時(shí)在整個(gè)實(shí)踐過(guò)程中我們?yōu)閷徍似脚_(tái)這個(gè)大型復(fù)雜項(xiàng)目前端引入了整潔架構(gòu)的思想,為后續(xù)的開(kāi)發(fā)維護(hù)提供了一種新的思路。沉淀了一套自動(dòng)化vue2代碼轉(zhuǎn)vue3 setup的工具,可為后臺(tái)項(xiàng)目的框架升級(jí)提供便利。同時(shí)借助AI能力提升前端E2E測(cè)試的效率,利用計(jì)算機(jī)視覺(jué)輔助前端UI自動(dòng)化測(cè)試。有幾點(diǎn)心得:
完美重構(gòu)、敏捷重構(gòu)、系統(tǒng)穩(wěn)定性難以平衡。
圖片
既然下定決心對(duì)年久失修的代碼進(jìn)行重構(gòu),我們一定是追求極致優(yōu)化和整潔的。但是需求現(xiàn)狀是不斷有新特性進(jìn)來(lái),戰(zhàn)線拉的太長(zhǎng)必將導(dǎo)致抹平差異的成本增加,因此敏捷性也很重要。同時(shí)底線是關(guān)注系統(tǒng)的可靠性和穩(wěn)定性。這三者一定程度上存在矛盾,需平衡:
整潔架構(gòu)非銀彈,容易過(guò)度設(shè)計(jì),學(xué)習(xí)門(mén)檻較高。
多倉(cāng)庫(kù)遷移路線:數(shù)據(jù)為支撐,測(cè)試用例先行,借助自動(dòng)代碼轉(zhuǎn)換工具和視覺(jué)輔助UI自動(dòng)化測(cè)試,制定合理的灰度策略并建立及時(shí)的故障反饋和響應(yīng)渠道。對(duì)于大型復(fù)雜項(xiàng)目或模塊,先進(jìn)行面向遷移的重構(gòu),也能起到事半功倍的作用。
對(duì)我們而言,遷移的結(jié)束只是起點(diǎn),基于更整潔的架構(gòu)和更先進(jìn)的前端框架,未來(lái)仍有很多發(fā)力點(diǎn):
本文鏈接:http://www.www897cc.com/showinfo-26-99167-0.html我們一起聊聊審核平臺(tái)前端新老倉(cāng)庫(kù)遷移
聲明:本網(wǎng)頁(yè)內(nèi)容旨在傳播知識(shí),若有侵權(quán)等問(wèn)題請(qǐng)及時(shí)與本網(wǎng)聯(lián)系,我們將在第一時(shí)間刪除處理。郵件:2376512515@qq.com
上一篇: AI 引領(lǐng)佛山智造 數(shù)聚禪城開(kāi)拓創(chuàng)新 2024 華為開(kāi)發(fā)者大會(huì)佛山分會(huì)場(chǎng)圓滿落幕
下一篇: Go語(yǔ)言助力安全測(cè)試:24小時(shí)內(nèi)發(fā)送5億次HTTP/1.1請(qǐng)求