We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
在低代码项目中,公共模块的复用度通常较高,为了提高代码质量,实施单元测试显得尤为重要。在持续迭代低代码平台的过程中,单元测试有助于提升自测效率,使我们能够尽早发现和修复潜在的 bug,从而保证项目的稳定性和可靠性。
运行环境
组件库相关配置
Vite 插件
@vitejs/[email protected]
我们选择使用 Jest 搭配 @vue/test-utils 进行单元测试。需要注意的是,由于 Vitest 需要 Vite 版本大于等于 5.0.0,Node 版本则需要大于等于 18.0.0,因此本项目并未采用该方案。
以下命令将安装所需的依赖:
pnpm add [email protected] @vue/[email protected] @vue/[email protected] [email protected] [email protected] -D
处理 Vite 环境的 import.env:
pnpm add [email protected] -D
处理 scss 文件转换
module.exports = { presets: [ ['@babel/preset-env', { targets: { node: 'current' } }], [ 'babel-preset-vite', { env: true, glob: false } ] ] }
module.exports = { testEnvironment: 'jsdom', transform: { '^.+\\.vue$': '@vue/vue3-jest', '^.+\\.js$': 'babel-jest', '^.+\\.scss$': 'jest-scss-transform' }, testMatch: ['**/tests/**/*.spec.js'], moduleNameMapper: { '^@/(.*)$': '<rootDir>/src/$1', '^h5-render(.*)$': '<rootDir>/.ssg/index.js', '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': '<rootDir>/tests/__mocks__/assetsTransformer.js', '\\.(css|less)$': '<rootDir>/assetsTransformer.js' }, collectCoverage: true, collectCoverageFrom: ['src/components/**/*.vue', 'src/modules/**/*.vue'] }
test
{ "scripts": { "test": "jest" } }
<template> <div class="c-demo"> <img @click="$emit('clickImg', imgurl)" :src="imgurl" alt="" /> <span @click="$emit('clickText', text)">{{ text }}</span> </div> </template> <script> export default { props: { imgurl: String, text: String }, created() { // console.log(this.$shared) // ! 全局共享数据 // console.log('urlParams:', urlParams) // ! 全局链接参数 // * 注册全局共享数据 this.$shared.registerSharedData('demo', { imgurl: this.imgurl, text: this.text }) // // * 注册全局共享方法 this.$shared.registerSharedFn('getDemoInfo', this.getDemoInfo) this.$shared.registerSharedFn('previewImg', this.previewImg) }, methods: { getDemoInfo() { console.log('getDemoInfo') }, previewImg() { console.log('previewImg') } } } </script>
import { mount } from '@vue/test-utils' import { createShared } from 'h5-render' import DemoComp from '@/components/base/demo/demo.vue' test('renders a demo comp', () => { window.urlParams = { link: 'vue' } const wrapper = mount(DemoComp, { props: { text: 'Learn Vue.js 3', imgurl: 'https://test-utils.vuejs.org/logo.svg' }, global: { mocks: { $shared: createShared() } } }) expect(wrapper.find('span').text()).toBe('Learn Vue.js 3') expect(wrapper.get('img').html()).toContain( 'https://test-utils.vuejs.org/logo.svg' ) })
<template> <van-popup v-model:show="show" id="renewal-verify-dialog" position="center" :lock-scroll="true" > <div id="dialog"> <i class="close-icon" @click="cancel"></i> <img src="./img/bg.png" alt="" /> <div class="content"> <div class="renewal"> <p>说明文案</p> </div> <div class="link" @click="readAgreement">《自动服务协议》</div> <div class="btns"> <div class="btn-open" @click="open">立即开启</div> <div class="btn-close"><span @click="cancel">暂不开启</span></div> </div> </div> </div> </van-popup> </template> <script setup> import { ref } from 'vue' import '@/scss/default/overlay.scss' import VanPopup from '@/vant/popup' import { drawer } from '@/common/components/drawer/index' // // 始终展示 const show = ref(true) const props = defineProps({ // 点击事件 handleAction: Function }) // 点击自动服务协议 const readAgreement = () => { drawer.open({ title: '自动服务协议', content: '' }) } // 关闭弹窗 const close = () => { show.value = false } // 点击取消 const cancel = () => { props.handleAction('cancel') close() } const open = () => { props.handleAction('confirm') close() } </script>
import { mount } from '@vue/test-utils' import { createApp, nextTick, flushPromises } from 'vue' import CusComp from '@/components/biz/auto-renewal-dialog/auto-renewal-dialog.vue' test('renders a auto-renewal-dialog comp, click confirm btn', async () => { let retVal = '' const wrapper = mount(CusComp, { props: { handleAction: (ret) => { retVal = ret } } }) await nextTick() expect(wrapper.find('.btn-open').text()).toBe('立即开启') // https://test-utils.vuejs.org/guide/essentials/event-handling.html wrapper.find('.btn-open').trigger('click') expect(retVal).toEqual('confirm') }) test('renders a auto-renewal-dialog comp, click cancel btn', async () => { let retVal = '' const wrapper = mount(CusComp, { props: { handleAction: (ret) => { retVal = ret } } }) await nextTick() expect(wrapper.find('.btn-close span').text()).toBe('暂不开启') wrapper.find('.btn-close span').trigger('click') expect(retVal).toEqual('cancel') })
const schame = { _name: 'other-info', fillInfo: { title: '其他信息' }, paywayConfig: { list: [ '使用链接参数<b>payMode</b>进行配置', '无payMode参数: <em>不展示支付方式,默认微信</em>', 'payMode=0: <em>展示支付方式,仅展示微信(微信环境下,不展示支付方式)</em>', 'payMode=1: <em>展示两种支付方式:微信、支付宝,默认微信支付</em>', 'payMode=2: <em>展示两种支付方式:微信、支付宝,默认支付宝支付</em>', 'payMode=3: <em>展示支付方式,仅展示支付宝支付(支付宝环境下,不展示支付方式)</em>' ] }, paymentTypeConfig: { list: [ '使用链接参数<b>ct</b>进行配置', 'ct=M: <em>显示缴费方式, 默认月缴</em>', 'ct=Y: <em>显示缴费方式, 默认年缴</em>', 'ct=N: <em>只展示一个年缴的缴费方式,默认年缴</em>', '无ct参数: <em>显示缴费方式,默认月缴(同ct=M)</em>' ], badge: true }, renewalTypeConfig: { list: [ '使用链接参数<b>rt</b>进行配置', 'rt=O: <em>默认开</em>', 'rt=C: <em>默认关</em>', '无rt参数: <em>展示续保方式,默认开</em>' ], show: true, interceptor: true } }
import { mount } from '@vue/test-utils' import { createApp, nextTick } from 'vue' import { createPinia } from 'pinia' import { globalShared } from 'h5-render' import ModuleComp from '@/modules/other-info/other-info.vue' import { ClientOnly } from '../__mocks__/ClientOnly' import { mockWeixin, mockZhifubao } from '../__mocks__/ua' import registerBaseSharedFn from '@/common/base-shared-fn.js' // 注册测试环境需要的共享方法 registerBaseSharedFn(globalShared) test('renders other-info module: 链接参数 ct = N, 仅展示年缴, 默认年缴', async () => { window.urlParams = { ct: 'N' } const app = createApp(ModuleComp) const pinia = createPinia() app.use(pinia) const wrapper = mount(ModuleComp, { components: { ClientOnly }, props: { fillInfo: { title: '其他信息' }, renewalTypeConfig: {}, paymentTypeConfig: {} } }) expect(wrapper.find('.title p').text()).toBe('其他信息') // 等 dom 渲染一下 await nextTick() expect(wrapper.getComponent(ModuleComp).vm.paymentType).toBe('4') const html = wrapper.html() expect(html).toContain('全额缴费') expect(html).not.toContain('按月缴费') }) test('renders other-info module: 链接参数 payMode = 3, 环境优先(在微信环境), 不展示支付方式 + 默认微信', async () => { window.urlParams = { payMode: '3' } mockWeixin() const app = createApp(ModuleComp) const pinia = createPinia() app.use(pinia) const wrapper = mount(ModuleComp, { components: { ClientOnly }, props: { fillInfo: { title: '其他信息' }, renewalTypeConfig: {}, paymentTypeConfig: {} } }) // 等 dom 渲染一下 await nextTick() // 不展示 expect(wrapper.getComponent(ModuleComp).vm.paywayConfig.show).toBe(false) // 不展示两种支付方式, 默认微信 expect(wrapper.getComponent(ModuleComp).vm.payway).toBe('1') const html = wrapper.html() expect(html).not.toContain('微信') expect(html).not.toContain('支付宝') }) // ... 其他测试用例略
const path = require('path') module.exports = { process(src, filename, config, options) { return 'module.exports = ' + JSON.stringify(path.basename(filename)) + ';' } }
import { defineComponent, useSlots, ref, onMounted } from 'vue' export const ClientOnly = defineComponent(function ClientOnly() { const slots = useSlots() const mount = ref(false) onMounted(() => { mount.value = true }) return () => (mount.value ? slots.default() : null) })
// 模拟 微信 userAgent export const mockWeixin = () => { Object.defineProperty(window.navigator, 'userAgent', { value: 'Mozilla/5.0 (Linux; Android 10; SM-G9730) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.87 Mobile Safari/537.36 MicroMessenger/8.0.1.1950(0x2800013D)', configurable: true // 允许后续修改 }) } // 模拟 支付宝 userAgent export const mockZhifubao = () => { Object.defineProperty(window.navigator, 'userAgent', { value: 'Mozilla/5.0 (Linux; Android 10; SM-G9730) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 AliApp(AlipayClient) 10.2.36.0512000 AWE/0.5.0.4 Mobile Safari/537.36', configurable: true // 允许后续修改 }) }
指标说明
开启单元测试校验时机
不在提交代码时做校验,而在部署发布时。
{ "scripts": { "build": "npm run test && sh ./build.sh", "test": "jest" } }
Node 内存溢出问题修复
以进行全局设置: 【1】打开一个cmd窗口; 【2】跑setx NODE_OPTIONS --max_old_space_size=10240; 【3】关闭所有cmd代码编辑器; 【4】重新打开cmd并再次运行节点命令(npm等);
The text was updated successfully, but these errors were encountered:
No branches or pull requests
单元测试的重要性
在低代码项目中,公共模块的复用度通常较高,为了提高代码质量,实施单元测试显得尤为重要。在持续迭代低代码平台的过程中,单元测试有助于提升自测效率,使我们能够尽早发现和修复潜在的 bug,从而保证项目的稳定性和可靠性。
低代码环境
运行环境
组件库相关配置
Vite 插件
@vitejs/[email protected]
@vitejs/[email protected]
@vitejs/[email protected]
初始化测试环境
技术方案
我们选择使用 Jest 搭配 @vue/test-utils 进行单元测试。需要注意的是,由于 Vitest 需要 Vite 版本大于等于 5.0.0,Node 版本则需要大于等于 18.0.0,因此本项目并未采用该方案。
安装依赖
以下命令将安装所需的依赖:
处理 Vite 环境的 import.env:
处理 scss 文件转换
配置文件
test
命令简单组件示例
src\components\base\demo\demo.vue
tests\components\demo.spec.js
src\components\biz\auto-renewal-dialog\auto-renewal-dialog.vue
tests\components\auto-renewal-dialog.spec.js
复杂模块示例
src\modules\other-info\other-info.vue
模块功能概述
tests\modules\other-info.spec.js
测试结果与覆盖率情况
指标说明
开启单元测试校验时机
不在提交代码时做校验,而在部署发布时。
Node 内存溢出问题修复
以进行全局设置:
【1】打开一个cmd窗口;
【2】跑setx NODE_OPTIONS --max_old_space_size=10240;
【3】关闭所有cmd代码编辑器;
【4】重新打开cmd并再次运行节点命令(npm等);
参考
The text was updated successfully, but these errors were encountered: