Vue Test Utils

2023-11-02

单元测试(unit testing),是指对软件中的最小可测试单元进行检查和验证。在提供了经过测试的单元的情况下,系统集成过程将会大大地简化。开发人员可以将精力集中在单元之间的交互作用和全局的功能实现上,而不是陷入充满很多Bug的单元之中不能自拔。

流行框架

1. jest

​Jest 是一个由 facebook 开发的测试运行器,内置Jsdom、断言库和mock,vue-test-utils 官网评价

  • jsdom 由纯javascript实现的一系列 web标准,特别是 WHATWG(Web Hypertext Application Technology Working Group /Web超文本应用技术工作组)组织制定的DOM和HTML标准, 项目的目标是模拟足够的Web浏览器子集

Jest 是功能最全的测试运行器。它所需的配置是最少的,默认安装了 JSDOM,内置断言且命令行的用户体验非常好。不过你需要一个能够将单文件组件导入到测试中的预处理器。我们已经创建了 vue-jest 预处理器来处理最常见的单文件组件特性,但仍不是 vue-loader 100% 的功能。

2. karma + mocha + chai + sinon

Karma 是一个启动浏览器运行测试并生成报告的测试运行器

Mocha 是一个在node.js 和浏览器上运行的功能丰富的JavaScript测试框架, 能良好的支持javascript异步的单元测试

Chai 是一个适用node.js和浏览器端的 BDD(行为) / TDD 断言库

  • TDD (Test-Driven Development) 测试驱动开发,注重测试逻辑

  • BDD (Behaviour-Driven Development) 行为驱动开发, 注重输出结果

Sinon 是一个独立的 JavaScript 测试spy, stub, mock库,没有依赖任何单元测试框架工程

  • spy (监听) 生成一个间谍函数,记录下函数调用的参数,返回值,this的值,以及抛出的异常

  • stub (存根) 用简单的行为替换复杂的行为,例如父子组件调用,但是子组件没有完成,可以用一个stub替代,告诉父组件这里有一个子组件

  • mock (模仿)生成代码中使用的假数据对象,验证预期

vue test utils

vue test utils 是 Vue.js 官方的单元测试实用工具库, 支持主流js测试运行器,是一个基于包裹器的 API,同步触发事件。因此 Vue.nextTick 不是必须的。

安装

vue.2.0老项目默认已经安装并配置好了 webpack、vue-loader 和 Babel,可以通过下面的方法添加测试模块

1. 使用 jest 测试单文件组件

安装依赖

​# 安装 jest @vue/test-utils
npm install --save-dev jest @vue/test-utils

# 安装vue-jest, 在 Jest 中处理vue文件需要安装 vue-jest 预处理器:
npm install --save-dev vue-jest

# 安装 babel-jest
npm install --save-dev babel-jest

# 安装 jest 快照插件
npm install --save-dev jest-serializer-vue

在根目录新建 test/unit 目录,添加 jest 配置文件 jest.conf.js

const path = require('path')

module.exports = { 
  rootDir: path.resolve(__dirname, '../../'),  
  moduleFileExtensions: [   
    'js',    
    'json',    
    // 告诉 Jest 处理 `*.vue` 文件
    'vue'
  ],  
  moduleNameMapper: {   
     // 用 `vue-jest` 处理 `*.vue` 文件
    '^@/(.*)$': '<rootDir>/src/$1'
  },  
  transform: {  
    '^.+\\.js$': '<rootDir>/node_modules/babel-jest', // 用 `babel-jest` 处理 `*.js` 文件
    '.*\\.(vue)$': '<rootDir>/node_modules/vue-jest' // 用 `vue-jest` 处理 `*.vue` 文件
  }, 
  snapshotSerializers: ['<rootDir>/node_modules/jest-serializer-vue'], // 快照插件
  setupFiles: ['<rootDir>/test/unit/setup'], // 在每次测试之前运行一些代码来配置或设置测试环境
  verbose: true,  
  testURL: 'http://localhost/', 
  collectCoverage: true, // 打开代码覆盖率收集功能
  coverageDirectory: '<rootDir>/test/unit/coverage', // 生成代码覆盖率报告文件夹目录
  // 定义需要收集测试覆盖率信息的文件路径
  collectCoverageFrom: [   
    'src/**/*.{js,vue}',    
    '!src/main.js',    
    '!src/router/index.js',    
    '!**/node_modules/**'
  ],  
  // 测试覆盖率报告模版
  coverageReporters: ['html', 'text-summary']
}

配置 test/unit/setup.js 文件, 不是必须

import Vue from 'vue'
// 去除生产环境警告
Vue.config.productionTip = false

配置测试环境 .babelrc

{
  ...
  "env": {
    "test": {
      "presets": [
        ["env", { "targets": { "node": "current" }}]
      ]
    }
  }
}

配置 package.json 测试脚本

// package.json
{
  "scripts": {
    "unit": "jest --config test/unit/jest.conf.js --coverage --updateSnapshot"
  }
}

当被测试内容修改后,重新生成快照片会报错,加上 --updateSnapshot 可以消除

测试文件可以放在 test/unit/spec/ 文件下,命名方式如 *.spec.js 或 *.test.js,jest 会识别自动识别类似文件, 运行下面代码启动测试脚本

npm run unit

2. 使用 Mocha 和 webpack 测试单文件组件

mocha-webpack 是一个 webpack + Mocha 的包裹器,同时包含了更顺畅的接口和侦听模式。这些设置的好处在于我们能够通过 webpack + vue-loader 得到完整的单文件组件支持,但这本身是需要很多配置的

npm install --save-dev @vue/test-utils mocha mocha-webpack

配置 package.json 测试脚本

// package.json
{
  "scripts": {
    "test": "mocha-webpack --webpack-config webpack.config.js --require test/setup.js test/**/*.spec.js"
  }
}

3. 使用 karam + mocha + chai + sinon

3.1 安装依赖

# 安装 @vue/test-utils
npm install --save-dev @vue/test-utils 
# 安装 mocha 和 chai 和 sinon
npm install --save-dev mocha chai sinon sinon-chai
# 安装 karma 依赖
npm install --save-dev karma karma-mocha karma-sourcemap-loader karma-spec-reporter karma-webpack karma-sinon-chai
# 使用 karma-coverage 插件来设置 Karma 的代码覆盖率
npm install --save-dev karma-coverage cross-env

3.2 webpack 配置

// config/test.conf.js
'use strict'
const merge = require('webpack-merge')
const devEnv = require('./dev.env')

module.exports = merge(devEnv, { 
  NODE_ENV: '"testing"'
})
// config/test.conf.js
'use strict'
const utils = require('./utils')
const webpack = require('webpack')
const merge = require('webpack-merge')
const baseWebpackConfig = require('./webpack.base.conf')
const webpackConfig = merge(baseWebpackConfig, { 
  module: {  
    rules: utils.styleLoaders()
  }, 
  devtool: '#inline-source-map',  
  resolveLoader: {   
    alias: {   
      'scss-loader': 'sass-loader'
    }
  }, 
  plugins: [  
    new webpack.DefinePlugin({   
       'process.env': require('../config/test.env')
    })
  ]
})
delete webpackConfig.entry
module.exports = webpackConfig

3.3.1 使用真实浏览器作为运行环境,例如 chrome, 需要 安装 chrome 启动器

npm install --save-dev karma-chrome-launcher babel-plugin-istanbul

创建 test/unit/karma.conf.js 文件:

// karma.conf.js
var webpackConfig = require('../../build/webpack.test.conf')

module.exports = function karmaConfig (config) {
  config.set({   
     browsers: ['Chrome'],    
     frameworks: ['mocha', 'sinon-chai', 'istanbul'],    
     reporters: ['spec', 'coverage'],    
     files: ['./index.js'],    
     preprocessors: {   
        './index.js': ['webpack', 'sourcemap']
     },    
     webpack: webpackConfig,    
     webpackMiddleware: {    
       noInfo: true
     },   
     coverageReporter: {  
         dir: './coverage',      
         reporters: [
            { type: 'lcov', subdir: '.' },
            { type: 'text-summary' }
         ]
     }
   })
 }

更新 .babelrc

{
  ...
  "env": {
    "test": {
      "presets": ["env", "stage-2"],
      "plugins": ["transform-vue-jsx"] 
    }
  }
}

3.3.2 使用无界面浏览器,例如 phantomjs

npm install --save-dev karma-phantomjs-launcher karma-phantomjs-shim phantomjs-prebuilt

创建 test/unit/karma.conf.js 文件:

// karma.conf.js
var webpackConfig = require('../../build/webpack.test.conf')

module.exports = function karmaConfig (config) {
  config.set({  
    browsers: ['PhantomJS'], // 'Chrome'
    frameworks: ['mocha', 'sinon-chai', 'phantomjs-shim'],    
    reporters: ['spec', 'coverage'],    
    files: ['./index.js'],    
    preprocessors: {    
      './index.js': ['webpack', 'sourcemap']
    },    
    webpack: webpackConfig,    
    webpackMiddleware: {   
       noInfo: true
    },    
    coverageReporter: {   
       dir: './coverage',      
       reporters: [
         { type: 'lcov', subdir: '.' },
         { type: 'text-summary' }
       ]
     }
  })
}

更新 .babelrc

{
  ...
  "env": {
    "test": {
      // 环境变量 添加 istanbul 插件
      "presets": ["env", "stage-2"],
      "plugins": ["transform-vue-jsx", "istanbul"] 
    }
  }
}

3.4 创建 test/unit/index.js 文件:

import Vue from 'vue'
// 关闭生成环境提示
Vue.config.productionTip = false

// 测试文件的目录
const testsContext = require.context('./specs', true, /\.spec$/)
testsContext.keys().forEach(testsContext)

// 测试覆盖率的文件范围,src文件下除了main.js
const srcContext = require.context('../../src', true, /^\.\/(?!main(\.js)?$)/)
srcContext.keys().forEach(srcContext)

3.5 创建 test/unit/.eslintrc 文件, 不然会运行时 expect 会报 undefined

{  
  "env": { 
    "mocha": true
   },  
  "globals": { 
    "expect": true,    
    "sinon": true
   }
}

3.6 定义 package.json 测试脚本

// package.json
{
  "scripts": {
    "unit": "cross-env BABEL_ENV=test karma start test/unit/karma.conf.js --single-run",
  }
}
  • --single-run 标识告诉了 Karma 一次性运行该测试套件

2. 使用 vue-cli 3.0

安装 vue-cli 3.0

npm install -g @vue/cli
# or
yarn global add @vue/cli

创建一个项目

vue create my-project
# or
vue ui // 启动创建项目的ui界面,默认8000端口
# 兼容2.0用法
vue init webpack my-project

选择默认或者手动模式

Vue CLI v3.0.1
? Please pick a preset: 
  default (babel, eslint) 
❯ Manually select features 

按空格键选中需要安装的功能,按回车下一步

Vue CLI v3.0.1
? Please pick a preset: Manually select features
? Check the features needed for your project: 
 ◉ Babel 
  ◯ TypeScript 
  ◯ Progressive Web App (PWA) Support 
  ◉ Router 
  ◉ Vuex 
  ◉ CSS Pre-processors 
  ◉ Linter / Formatter
 ❯◉ Unit Testing 
  ◯ E2E Testing

选择测试工具,这里内置了两种方式

  1. 选择 jest, 在jest.conf.js 添加代码覆盖率报告和快照设置,记得安装 jest-serializer-vuenpm包

module.exports = {  
  // ...
  snapshotSerializers: ['<rootDir>/node_modules/jest-serializer-vue'], // 快照插件, 需要安装
  collectCoverage: true,  
  coverageDirectory: '<rootDir>/tests/unit/coverage',  
  collectCoverageFrom: [   
      '**/*.{js,vue}',    
      '!**/node_modules/**'
  ],  
  coverageReporters: [  
     'html',   
     'text-summary'
  ]
}
  1. 选择 mocha + chai, 在karma.conf.js 添加代码覆盖率报告

module.exports = {
  ...
  reporters: ['spec', 'coverage'],  
  coverageReporter: {   
     dir: './coverage',    
     reporters: [
      { type: 'lcov', subdir: '.' },
      { type: 'text-summary' }
     ]
  }
}

 

使用

这里以jest 为例

测试普通js文件

创建 utils/index.js

function sum (a, b) {
   return a + b
}
function minus (a, b) { 
   return a - b
}
module.exports = {
  sum,
  minus
} 

创建 test/spec/sum.spec.js

import { sum, minus } from '@/utils/index.js'

test('adds 1 + 2 to equal 3', () => {
  expect(sum(1, 2)).toBe(3)
})

test('adds 2 - 1 to equal 1', () => {
  expect(minus(2, 1)).toBe(1)
})

测试 vue 文件

创建 src/components/HelloWorld.vue

<template>
  <div class="hello">
    <h1>{{ msg }}</h1>
    <my-counter></my-counter>
  </div>
</template>

<script>
import MyCounter from './MyCounter'

export default { 
  name: 'HelloWorld',
  data () {   
    return {    
      msg: 'Welcome to Your Vue.js App'
    }
  }, 
  components: {
    MyCounter
  }
}</script>

创建 src/components/Counter.vue

<template>
  <div class="my-counter">
    <p style="margin-top: 10px">计算: {{base}} + {{ count }} = {{total}}</p>
    计算基数: <input class="my-input" id="input" type="text" v-model="inputValue">
    <button class="my-button" @click="increment" ref="my-button" name="my-button">{{text}}</button>
    <slot></slot>
    <slot name="foo"></slot>
  </div>
</template>

<script>
export default { 
  name: 'MyCounter',  
  props: {    
    text: {    
      type: String,      
      default: 'Increment'
    }
  },
  data () {  
     return {     
       count: 0,      
       inputValue: 2,      
       base: 2
    }
  }, 
  methods: {
    increment () { 
      this.count++
    }
  },  
  computed: {
    total () {    
      return parseInt(this.base) + this.count
    }
  },  
  watch: {
    inputValue () {  
      if (this.inputValue) {      
        this.base = this.inputValue
      }
    }
  }
}
</script>
<style>
.my-button {
  width: 100px;  
  height: 40px;  
  border: none;  
  border-radius: 4px;  
  background: #e83e43;  
  color: #fff;  
  font-size: 18px;  
  outline: none;
}
.my-input {
  width: 80px;  
  height: 30px;  
  font-size: 18px;  
  outline: none;
}
</style>

npm run dev 运行后,页面正常显示

测试DOM结构

通过mount、shallow、find、findAll方法都可以返回一个包裹器对象,包裹器会暴露很多封装、遍历和查询其内部的Vue组件实例的便捷的方法。

find和findAll方法都可以都接受一个选择器作为参数,find方法返回匹配选择器的DOM节点或Vue组件的Wrapper,findAll方法返回所有匹配选择器的DOM节点或Vue组件的Wrappers的WrapperArray。

很多方法的参数中都包含选择器。一个选择器可以是一个 CSS 选择器、一个 Vue 组件或是一个查找选项对象。

选择器

CSS 选择器

挂载处理任何有效的 CSS 选择器:

  • 标签选择器 (div、foo、bar)

  • 类选择器 (.foo、.bar)

  • 特性选择器 ([foo]、[foo="bar"])

  • id 选择器 (#foo、#bar)

  • 伪选择器 (div:first-of-type)

你也可以结合使用:

  • 直接从属结合 (div > #bar > .foo)

  • 一般从属结合 (div #bar .foo)

  • 近邻兄弟选择器 (div + .foo)

  • 一般兄弟选择器 (div ~ .foo)

Vue 组件

Vue 组件也是有效的选择器

// 'test/unit/specs/helloWorld.spec.js'
import MyCounter from '@/components/MyCounter.vue'

const myCounter = wrapper.find(MyCounter)
  expect(myCounter.is(MyCounter)).toBe(true)
})

查找选项对象:

Name:可以根据一个组件的name选择元素

// test/unit/specs/helloWorld.spec.js
import MyCounter from '@/components/MyCounter.vue'

const myCounter = wrapper.find({ name: 'MyCounter' })
expect(myCounter.is(MyCounter)).toBe(true)

Ref:可以根据$ref选择元素。wrapper.find({ ref: 'my-button' })

// test/unit/specs/myCounter.spec.js
wrapper.find({ ref: 'my-button' }).trigger('click')

创建 test/unit/specs/helloWorld.spec.js

// wrapper.vm 属性访问一个实例所有的方法和属性

import { shallowMount, mount } from '@vue/test-utils'
import HelloWorld from '@/components/HelloWorld.vue'

describe('测试 HelloWorld 组件', () => {
  test('是一个 Vue 实例', () => { 
    const wrapper = shallowMount(HelloWorld)
    expect(wrapper.isVueInstance()).toBeTruthy()
  })
  test('mount 快照', () => {  
    const wrapper = mount(HelloWorld)
    expect(wrapper.vm.$el).toMatchSnapshot()
  })
  test('shallowMount 快照', () => {  
    const wrapper = shallowMount(HelloWorld)
    expect(wrapper.vm.$el).toMatchSnapshot()
  })
})

创建 test/unit/specs/myCounter.spec.js

import { shallowMount } from '@vue/test-utils'
import MyCounter from '@/components/MyCounter.vue'

describe('MyCounter.vue', () => {
  test('测试加一', () => {  
    const wrapper = shallowMount(MyCounter)
    wrapper.find('button').trigger('click')
    expect(wrapper.find('p').text()).toBe('1')
  })
})

执行单元测试后,测试通过,然后Jest会在 test/__snapshots__/ 文件夹下创建一个快照文件 helloWorld.spec.js.snap

// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`测试 HelloWorld 组件 mount 快照 1`] = `
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`测试 HelloWorld 组件 mount 快照 1`] = `
<div
  class="hello"
>
  <h1>
    Welcome to Your Vue.js App 
  </h1>
   
  <div
    class="my-counter"
  >
    <p
      style="margin-top: 10px;"
    >
      计算: 2 + 0 = 2    
    </p>
    
  计算基数: 
    <input
      class="my-input"
      id="input"
      type="text"
    />
     
    <button
      class="my-button"
      name="my-button"
    >
      Increment    
    </button>
      
  </div>
</div>
`;

exports[`测试 HelloWorld 组件 shallowMount 快照 1`] = `
<div
  class="hello"
>
  <h1>
    Welcome to Your Vue.js App 
  </h1>
   
  <mycounter-stub
    text="Increment"
  />
</div>
`;

通过对比发现 shallowMmount 方法,它和mount一样,创建一个包含被挂载和渲染的Vue组件的Wrapper,不同的是子组件被存根的替代,不被渲染

测试样式

UI的样式测试为了测试我们的样式是否复合设计稿预期。同时通过样式测试我们可以感受当我们code变化带来的UI变化,以及是否符合预期。

// 'test/unit/specs/myCounter.spec.js'
// classes 返回 Wrapper DOM 节点的 class。
test('组件有my-button类', () => { 
  const wrapper = shallowMount(MyCounter)  
  const myButton = wrapper.find({ ref: 'my-button' })
  expect(myButton.classes()).toContain('my-button')
})

测试Props

在挂载时向组件传值 propsData 或 使用 setProps() 方法

// 'test/unit/specs/myCounter.spec.js' 
// props() 返回 Wrapper vm 的 props 对象
test('测试props', () => {
  const wrapper = shallowMount(MyCounter, {  
    propsData: {   
      text: '递增'
    }
  })  
  // const wrapper = shallowMount(MyCounter)
  // wrapper.setProps({text: '递增'})
  expect(wrapper.props().text).toBe('递增')
})

测试计算属性

// 'test/unit/specs/myCounter.spec.js' 
test('测试计算属性', () => {
  const wrapper = shallowMount(MyCounter)
  wrapper.vm.count = 3
  expect(wrapper.vm.total).toBe(5)
})

测试监听器

vue-test-utils 同步触发事件,断言不用放到$nextTick中

// 'test/unit/specs/myCounter.spec.js' 
test('测试计算属性', () => { 
  const wrapper = shallowMount(MyCounter)
  wrapper.vm.inputValue = 5
  expect(wrapper.vm.total).toBe(5)
})

mock依赖

伪造全局注入的时候有用

// 'test/unit/specs/helloWorld.spec.js'
test('测试mock', () => { 
  const $store = { authed: true }  
  const wrapper = shallowMount(HelloWorld, { 
    mocks: {
      $store
    }
  })
  expect(wrapper.vm.$store.authed).toBe(true)
})

测试插槽 slots

类型:{ [name: string]: Array<Component>|Component|string } 为组件提供一个 slot 内容的对象。该对象中的键名就是相应的 slot 名,键值可以是一个组件、一个组件数组、一个字符串模板或文本。

// 'test/unit/specs/MyCounter.spec.js'
test('测试slot', () => {
  const wrapper = shallowMount(MyCounter, {
    slots: {    
      default: '<p class="default">匿名slot</p>',      
      foo: '<p class="foo">slot name of foo</p>'
    }
  })
  expect(wrapper.find('.default').html()).toBe('<p class="default">匿名slot</p>')
  expect(wrapper.find('.foo').html()).toBe('<p class="foo">slot name of foo</p>')
})

销毁一个 Vue 组件实例

destroy() 销毁 WrapperArray 中的每个 Vue Wrapper 并且可以使用 beforeEach()、

describe('销毁', () => { 
  let wrapper;
  beforeEach(() => {
    wrapper = shallowMount(Component);
  })
  afterEach(() => {
    wrapper.destroy()
  })

  test('test1', () => {  
    // 直接调用 wrapper
  })
  test('test2', () => {  
    // // 直接调用 wrapper
  })

 

 

 

 

 

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Vue Test Utils 的相关文章

随机推荐

  • OD华为机试 32

    组成最大数 描述 小组中每位都有一张卡片 卡片上是6位内的正整数 将卡片连起来可以组成多种数字 计算组成的最大数字 输入描述 号分割的多个正整数字符串 不需要考虑非数字异常情况 小组最多25个人 输出描述 最大的数字字符串 示例一 输入 2
  • elk 笔记15--mapping

    elk 笔记15 mapping 1 mapping 简介 1 1 什么是mapping 1 2 为何移除多type 2 mapping 数据类型 3 mapping 元字段 4 mapping 参数 5 动态 mapping 6 常见 m
  • 3.Unity Shader 基础

    目录 一对好兄弟 材质和Unity Shader Unity中shader ShaerLab Unity Shader 的结构 1 创建 2 Properties 3 SubShader 状态设置 RenderSetup SubShader
  • hive永久注册udf函数小例子

    来源 我是码农 转载请保留出处和链接 本文链接 http www 54manong com id 1217 注册UDF函数 1 helloUDF java中package信息需要修改 代码如下 package com maven udf p
  • ChatGLM2_6b安装

    Chatglm2 6b安装 一 安装要求 1 硬件 能否使用 或者以什么模式使用主要取决于显卡的显存 2 能否使用AMD显卡 可以 甚至可以使用CPU 但是需要降低精度 以CPU模式运行大概需要32GB 内存 二 工程与下载 官方路径 工程
  • 遇到问题之-yum update无法连接镜像问题解决

    yumupdate出现无法解析的错误 查看yum源设置也正确 然后查看本机DNS无法解析 通过防火墙开放DNS端口解决 然后还是无法yum安装 查看另外一台服务器和故障机配置都相同 但是yum正常 发现两者不同的是load的mirror是不
  • 物联网mqtt消息格式定义示例

    物联网mqtt消息格式定义示例 物联网mqtt通信流程 消息定义 应用下发到云 POST https 云服务器ip iot product id device id cmd Content Type application json X A
  • Spring MVC应用的开发步骤

    Spring MVC应用的开发步骤 Spring MVC应用的开发步骤 如果以异步方式提交请求 利用XML配置文件 配置控制器类 Spring MVC应用的开发步骤 下面简单介绍Spring MVC应用的开发步骤 在web xml文件中配置
  • Frida hook Java层

    往期推荐 frida环境安装 Xposed框架安装 使用及插件开发 HOOK startActivity HOOK框架 动态代理 需要相关资料的朋友 可以 加入此处即可打包获取 需要对在进程外传递的消息进行拦截处理就必须采取一种称为HOOK
  • Hexo博客开发之——七牛云图片批量下载

    一 前言 基于hexo Github搭建的静态博客 图片等资源的存储是一个问题 之前考虑过图片保存本地文件夹 图床 qiniu 微博 等 但是七牛存储的图片会有一些问题 使得我不得不考虑领寻图片等资源的存储问题 1 1 七牛图片存储的问题
  • UE4 加载流关卡(StreamingLevel)

    主要内容 本文主要讲解流关卡的加载卸载以及使用流关卡的好处 使用流关卡的好处在于利于多人协同合作 例如使用SVN进行项目开发时 各自上传或修改各自负责的关卡可以互不干扰 实现步骤 1 新建一个默认的主关卡和两个空的关卡 2 在主关卡里把两个
  • 解决 Ubuntu18.10 无法安装 Nvidia-docker2

    2023年5月18日更新 nvidia 原 github io 网页已废弃 更新了新的主页链接 2022年8月10日更新 很高兴看到这篇文章能够帮助一些同仁 在此更新一下 根据 https nvidia github io nvidia d
  • iOS核心动画CoreAnimation系统进阶(赛贝尔曲线-菜单侧滑动画拆分动画详解)

    我们知道动画是基于绘制的 多次绘制贝塞尔的过程就会形成动画 流畅的动画效果会给用户带来不一样的使用体验 下面我们就让App开发中经常使用到的侧滑动画进行拆分详解 效果图如下 为侧滑动画封装一个slideMenuView 绘制侧滑动画需要下面
  • Raft协议处理各种failover情况

    Raft协议处理各种failover情况 Raft 协议的易理解性描述 虽然 Raft 的论文比 Paxos 简单版论文还容易读了 但论文依然发散的比较多 相对冗长 读完后掩卷沉思觉得还是整理一下才会更牢靠 变成真正属于自己的 这里我就借助
  • MYSQL数据库服务磁盘IO高问题分析与优化

    压力测试过程中 如果因为资源使用瓶颈等问题引发最直接性能问题是业务交易响应时间偏大 TPS逐渐降低等 而问题定位分析通常情况下 最优先排查的是监控服务器资源利用率 例如先用TOP 或者nmon等查看CPU 内存使用情况 然后在排查IO问题
  • java学生信息管理系统_毕业设计项目实例(含源码毕业论文)

    一 面向人群 学校的学生信息管理人员以及老师和学生 二 总体功能描述 本系统利用Java Web技术实现了学生信息管理系统 具有简单的学生信息管理功能 实现了以下功能模块 院系信息管理模块 学生信息管理模块 课程信息管理模块 成绩管理模块
  • http常见的特殊字符转义

    Http协议真的好严格 特殊字符诸如 等都会被当做转义字符处理 这样的话请求路径的参数就不对了 所以特殊字符要如下处理 空格 用 20代替 用 22代替 用 23代替 用 25代替 用 26代替 用 28代替 用 29代替 用 2B代替 用
  • python网页点击操作

    你好 在 Python 中 你可以使用 Selenium 库来进行网页点击操作 首先 你需要安装 Selenium pipinstall selenium 然后 你可以使用以下代码来打开浏览器并访问网页 from selenium impo
  • 结构体 & 枚举 在嵌入式开发中的巧妙使用

    结构体 枚举 提到结构体相信大家并不陌生 结构体顾名思义 是一种体 各种变量 的集合 方便了我们对于数据的管理和操作 在嵌入式开发中是必不可少要掌握的技能 那么我就通过举例子的方式给大家讲一下怎样使用结构体 第一种 普通式 我定义一个学生
  • Vue Test Utils

    单元测试 unit testing 是指对软件中的最小可测试单元进行检查和验证 在提供了经过测试的单元的情况下 系统集成过程将会大大地简化 开发人员可以将精力集中在单元之间的交互作用和全局的功能实现上 而不是陷入充满很多Bug的单元之中不能