当前位置: 首页 > news >正文

高质量前端之自动化测试

前端自动化测试:Testing Library 篇

引言

前端测试
静态测试 eslint、TypeScript
单元测试 jest、mocha
集成测试 enzyme、react-testing-library、mock
爬虫
前后端解耦

为什么要引入自动化测试

测试可以让开发者站在用户的角度考虑问题,通过测试的手段,确保组件的每个功能都可以正常地运行。

在编写单元测试时,很大情况下会对组件代码进行反复的调整,通过不断的打磨,避免了开发时考虑不周到的情况,从而提高组件的质量。对于变动不频繁的业务模块,也是同样的道理。

根据《Google 软件测试之道》一书中,对于测试认证级别的定义如下:

  • 级别 1

    • 使用测试覆盖率工具。
    • 使用持续集成。
    • 测试分级为小型、中型、大型。
    • 创建冒烟测试集合(主流程测试用例)。
    • 标记哪些测试是非确定性的测试(测试结果不唯一)。
  • 级别 2

    • 如果有测试运行结果为红色(失败)就不会发布。
    • 每次代码提交之前都要求通过冒烟测试。(自测,简单走下主流程)
    • 各种类型的整体代码覆盖率要大于50%。
    • 小型测试的覆盖率要大于10%。
  • 级别 3

    • 所有重要的代码变更都要经过测试。
    • 小型测试的覆盖率大于50%。
    • 新增重要功能都要通过集成测试的验证。
  • 级别 4

    • 在提交任何新代码之前都会自动运行冒烟测试。
    • 冒烟测试必须在30分钟内运行完毕。
    • 没有不确定性的测试。
    • 总体测试覆盖率应该不小于40%。
    • 小型测试的代码覆盖率应该不小于25%。
    • 所有重要的功能都应该被集成测试验证到。
  • 级别 5

    • 对每一个重要的缺陷修复都要增加一个测试用例与之对应。

    • 积极使用可用的代码分析工具。

    • 总体测试覆盖率不低于60%。

    • 小型测试代码覆盖率应该不小于40%。

测试工具的选择

Jest,Mocha,Testing Library 和 Enzyme 是前端自动化测试使用较广泛的工具。

可遵循的简单规则

AAA 模式:编排(Arrange)、执行(Act)、断言(Assert)

一、编排

编排阶段可分为两部分。渲染组件获取所需要的 DOM 元素

二、执行

当把需要测试的内容准备好了后,便可以借助测试库的工具 API 执行模拟操作了。如使用 React Testing LibraryfireEvent 模拟点击事件。(这一步非必须的,也可以直接对编排获取到的内容进行断言)

fireEvent.click(mockFn)

三、断言

断言也即是判断。通过 Jest 或 React Testing Library 内置的断言语句对上述内容进行判断,通过则 pass。

简介

官网手册

Testing Library 是一个轻量级的测试解决方案,用于通过查询和与 DOM 节点交互(无论是用 JsDOM / Jest 模拟还是在浏览器中)来测试网页。提供贴近于用户在页面中查找元素的方式查询节点。

Testing Library 可应用于各个主流框架(包括 ReactAngularVue),并且可作为核心在 Cypress 测试框架中使用。

本文探究 React Testing Library(RTL)

思想

Testing Library 鼓励测试避免 实现细节(比如组件的内部状态、内部方法、生命周期、子组件),而强调关注与用户实际交互相似的内容。

编写的测试与软件用户的使用方式越相似,它们越能给测试带来信心。

核心

查询

类型

RTL 中提供三种方式查询元素,分别是getByqueryByfindBy。如果要查询多元素,使用xxxAllBy 代替。差异如下:

查询类型0 对应1 对应>1 对应异步/等待 await
getBy…报错返回报错no
queryBy…null返回报错no
findBy…报错返回报错yes
getAllBy…报错返回数组返回数组no
queryAllBy…[ ]返回数组返回数组no
findAllBy…报错返回数组返回数组yes

通常来说,getBy 用于查询正常存在的元素(找不到报错),queryBy 用于查询希望不存在的元素(找不到不报错),findBy 则用于查询需要等待的异步元素。

使用 *ByRole 会将隐藏元素名称读取为 “”。可考虑更好另一种查询方法,如仍需使用 *ByRole 进行查询可参考官方手册。 问题详情

优先级

按照测试库的指导思想,在使用查询 API 时应该遵守一定的优先级(站在用户的使用角度,能直接用从页面上看到的作为选择器便不使用用户看不到的 id、class 等选择器)。

  • 常规的

    • getByRole 查找具有特定角色的元素

    • getByLabelText 查找具有给定文本匹配的 label 元素

    • getByPlaceholderText 查找具有占位符属性的元素

    • getByText 查找具有文本节点的元素

    • getByDisplayValue 查找具有 value 的控件元素

  • 语义查询

    • getByAltText 查找具有 alt 属性,alt 对应的 text 文本匹配的元素

    • getByTitle 返回具有 title 属性,title 对应的 text 文本匹配的元素

  • 借助测试 Id

    • getByTestId(考虑在生产环境中避免无意义的属性,可以借助 babel-plugin-react-remove-properties 去除 data-test 测试辅助选择器)

      // .bablerc
      {"env": {"production": {"plugins": ["react-remove-properties"]}}
      }
      

实践

由于 Test Library 强调站在用户的角度进行测试,因此更偏向使用 DOM 上的 Accessibility 的元素。(但有时候只能用样式选择器或直接使用样式选择器更有效率,只能说找到哲学与生活的平衡点就好)

查看 HTML 元素对应的 ARIA role 方法有以下几点:

一、借助 Chrome 开发者工具

元素(Elements) > 无障碍功能(Accessibility)

二、借助 RTL 提供的 logRoles API

import { render, logRoles } from '@testing-library/react';test('find ARIA role', () => {const { container } = render(<Component />);logRoles(container);
})

三、借助第三方插件

使用谷歌浏览器的插件可以更为简易地获取元素。

Testing library: which query

Testing Playground

用户操作

借助 fireEvent 可以模拟实际用户产生的交互事件。

Testing Library 下还有一个高级库@testing-library/user-event ,其提供了比 fireEvent 更多的交互事件。

fireEvent(node: HTMLElement, event: Event)

fireEvent 对应的 eventMap 事件集属性

常用断言

RTL 扩展了 jest 的 api,定义了自己的断言函数,所有的断言函数包含在@testing-library/jest-dom包中。详见:内置断言库

toBeDisabled
toBeEnabled
toBeEmptyDOMElement
toBeInTheDocument
toBeInvalid
toBeRequired
toBeValid
toBeVisible
toContainElement
toContainHTML
toHaveAttribute
toHaveClass
toHaveFocus
toHaveFormValues
toHaveStyle
toHaveTextContent
toHaveValue
toHaveDisplayValue
toBeChecked
toBePartiallyChecked
toHaveDescription

Jest

使用 RTL 需要搭配 Jest ,所以我们先来 “荒腔走板” 地对 Jest 基础使用进行一个简单的学习。

介绍

Jest是一个测试框架,RTL 是一个测试解决方案。RTL 存在的意义是通过更优美、功能更强大的方式去完成测试的编写。

断言匹配器

判断真假

  1. toBeNull

  2. toBeUndefined

  3. toBeDefined

  4. toBeTruthy

  5. toBeFalsy

数字相关

  1. toBeGeaterThan 大于某个数

  2. toBeGeaterThanOrEqual 大于或等于

  3. toBeLessThan 小于某数

  4. toBeLessThanOrEqual 小于或等于

  5. toBeCloseTo 浮点数的相等判断

not 修饰符

与期望相反的匹配

字符串匹配

  1. toMatch

数组,集合相关

  1. toContain 判断数组或集合是否包含某个元素

  2. toHaveLength 判断数组的长度

函数相关

  1. toHaveBeenCalled 判断 mockFunc 是否被调用

  2. toHaveBeenCalledWith(‘_test_param’) 调用接收的参数是否为 _test_param

  3. toHaveBeenCalledTimes(2) 调用的次数

  4. toHaveReturned 是否有返回值

  5. toHaveReturnedWith(‘_test_return’) 返回值是否为 _test_return

异常相关

  1. toThrow 判断抛出的异常是否符合预期

Mock 函数

Jest 的三个常用 Mock 函数 API 是 jest.fn()jest.spyOn()jest.mock()

在单元测试中,更多时候并不需要关心内部调用方法的执行过程和结果,只需要确认是否被正确调用即可。

Mock 函数提供三种特性:

  • 捕获函数调用情况
  • 设置函数返回值
  • 改变函数内部实现

一、Jest.fn()

默认返回 undefined 作为返回值。

const test_click = jest.fn(1,2,3);expect(test_click).toHaveBeenCalledTimes(1) // 测试是否调用了 1 次
expect(test_click).toHaveBeenCalledWith(1,2,3) // 测试是否调用了 1 次

也可以自定义返回值,内部实现

// 自定义返回值
let customFn = jest.fn().mockReturnValue('default');
expect(customFn()).toBe('default');// 自定义内部实现
let customInside = jest.fn((num1, num2) => num1 + num2);
expect(customInside(10, 10)).toBe(20);// 返回 Promise
test('jest 返回 Promise', async() => {let mockFn = jest.fn().mockResolveValue('promise');let result = await mockFn();expect(result).toBe('promise');expect(Object.prototype.toString.call(mockFn())).toBe('[object Promise]')
})

分组和钩子

使用 describe 关键字对测试进行包裹。

describe('分别测试', () => {/** testItem **//** hook 函数 **/
}

需要注意的是,describe 块的运行顺序总是在 test 函数的执行顺序之前。疑问?

// describe 块内程序的执行顺序()
describe('1', () => {console.log('1');describe('2', () => { console.log('2'); });describe('3', () => {console.log('3');describe('4', () => { console.log('4'); })describe('5', () => { console.log('5'); })})describe('6', () => { console.log('6'); })
})
describe('7', () => {console.log('7');it('(since there has to be at least one test)', () => { console.log('8') });
})// 测试结果,顺序 1-8

可利用 jest 提供的钩子函数对单例进行数据准备工作。

Jest 提供的钩子函数有:

  1. beforeEach

  2. beforeAll

  3. afterEach

  4. afterAll

需要注意describe 外部使用的钩子函数和在内部的钩子函数执行顺序:

beforeAll(() => console.log('outside beforeAll'));
beforeEach(() => console.log('outside beforeEach'));
afterAll(() => console.log('outside afterAll'));
afterEach(() => console.log('outside afterEach'));describe('', () => {beforeAll(() => console.log('intside beforeAll'));beforeEach(() => console.log('intside beforeEach'));afterAll(() => console.log('intside afterAll'));afterEach(() => console.log('intside afterEach'));test('test1', () => console.log('test1 run'));test('test2', () => console.log('test2 run'));
})/** 结果
outside beforeAll
inside beforeAlloutside beforeEach
inside beforeEach
test1 run
inside afterEach
outside afterEachoutside beforeEach
inside beforeEach
test2 run
inside afterEach
outside afterEachinside afterAll
outside afterAll
**/

测试异步模块

在包括测试的函数里传入 done 参数,Jest 会等 done 回调函数执行结束后结束测试。若 done 从未被调用,则测试用例执行失败,同时输出超时错误。

import timeout from './timeout'test('测试timer', (done) => {timeout(() => {expect(2+2).toBe(4)done()})
})

如果异步函数返回 Promise,则可以直接将这个 Promise 返回,Jest 会等待这个 Promise 的 resolve 状态。如果期待 Promise 被 Reject,则需要使用.catch方法,并添加expect.assertions来验证一定数量的断言被调用。

test('the data is peanut butter', () => {return fetchData().then(data => {expect(data).toBe('peanut butter');});
});test('the fetch fails with an error', () => {expect.assertions(1);return fetchData().catch(e => expect(e).toMatch('error'));
});test('the data is peanut butter', () => {return expect(fetchData()).resolves.toBe('peanut butter');
});test('the fetch fails with an error', () => {return expect(fetchData()).rejects.toMatch('error');
});

借助 async / await

test('the data is peanut butter', async () => {const data = await fetchData();expect(data).toBe('peanut butter');
});test('the fetch fails with an error', async () => {expect.assertions(1);try {await fetchData();} catch (e) {expect(e).toMatch('error');}
});

在测试 React 组件时,经常能碰到在 useEffect 中更新 State 或异步更新 State 的场景,这时测试断言的处理需要借助 Act API 。案例说明和使用方法

配置文件

// 一下列举常用配置项目,所有配置属性访问官网手册 https://jestjs.io/docs/configuration// 默认地,Jest 会运行所有的测试用例然后产出所有的错误到控制台中直至结束。
// bail 配置选项可以让 Jest 在遇到第一个失败后就停止继续运行测试用例,默认值 0
bail: 1,
// 每次测试前自动清除模拟调用和实例,相当于每次测试前都调用 jest.clearAllMocks,默认 false
clearMocks: false,
// 指示在执行测试时是否应收集覆盖率信息,通过使用覆盖率收集语句改造所有已执行文件,所以开启后可能会显著减慢
// 测试速度,默认 false
collectCoverage: false,
// 指定测试覆盖率统计范围,使用 glob 模式匹配,默认 undefined
collectCoverageFrom: ['src/**/*.{js,jsx,ts,tsx}', '!src/**/*.d.ts'],
// 指定忽略匹配的覆盖范围,默认 ["/node_modules"]
coveragePathIgnorePatterns: ["/node_modules/"],
// 配置一组在所有测试环境中可用的全局变量, 默认 {}
globals: {},
// 用于给模块路径映射到不同的模块, 默认 null
moduleNameMapper: {"\\.(css|less|scss|sss|styl)$": "jest-css-modules" | "identity-obj-proxy"
}
// 指定 Jest 根目录,Jest 只会在根目录下测试用例并运行
rootDir: './',
// 设置 Jest 搜索文件的目录路径列表,默认 []
roots: [],
// 指定创建测试环境前的准备文件,针对每个测试文件都会运行一次
setupFiles: ['react-app-polyfill/jsdom'],
// 指定测试环境创建完成后为每个测试文件编写的配置文件
setupFilesAfterEnv: ['<rootDir>/src/setupTests.ts'],
// 配置 Jest 匹配测试文件的规则, 使用 glob 规则
testMatch: ['<rootDir>/src/**/__tests__/**/*.{js,jsx,ts,tsx}','<rootDir>/src/**/*.{spec,test}.{js,jsx,ts,tsx}'
],
// 用于指定测试用例运行环境,默认 node
testEnvironment: 'jsdom',
// 配置文件处理模块应该忽略的文件
transformIgnorePatterns: ['[/\\\\]node_modules[/\\\\].+\\.(js|jsx|ts|tsx)$','^.+\\.module\\.(css|sass|scss)$'
],
// 指定转换器: js jsx ts tsx 使用 babel-jest 进行转换 css 使用 cssTransform.js 进行转换 其他文件使用 fileTransform.js
transform: {'^.+\\.(js|jsx|ts|tsx)$': '<rootDir>/node_modules/babel-jest','^.+\\.css$': '<rootDir>/config/jest/cssTransform.js','^(?!.*\\.(js|jsx|ts|tsx|css|json)$)':'<rootDir>/config/jest/fileTransform.js'
},
// 转化器忽略文件: node_modules 目录下的所有 js jsx ts tsx cssModule 中的所有 css sass scss
transformIgnorePatterns: ['[/\\\\]node_modules[/\\\\].+\\.(js|jsx|ts|tsx)$','^.+\\.module\\.(css|sass|scss)$'
],
// 测试超时时间,默认 5000 毫秒
testTimeout: 5000

实践与调试

当一个测试失败了,应该首先检查单独运行该测试用例是否失败。只需要将 test 改为 test.only 便可。

// 编写一条测试
test(describe, () => {render(<Component />);expect();
})// describe 把多条测试包裹起来进行分组
describe(describe, () => {test('test1', fn);test('test2', fn);test.only('test3', fn); // 再次运行测试,便只会对该单列进行测试
})

当需要在终端查看查找到的 Dom 元素时,使用 prettyDOM 对元素进行包括,便可浏览贴近 HTML 结构的结果,当传递 null 时,prettyDOM 返回整个文档的渲染结果。

const div = container.querySelector('div');
console.log(prettyDOM(div));

演示 Demo

仓库:React Testing Library

项目准备

npm i create-react-app -g // 全局安装脚手架create-react-app React-Testing-Library // 创建项目yarn add @types/react @types/react-dom --dev //  基于 TypeScriptyarn add axios // 使用 axios 处理异步请求

使用 create-react-app 脚手架创建的项目已经默认使用 Testing Library 作为测试方案,但是脚手架默认将工具的一些配置隐藏起来,如果希望将配置弹出并进行手动配置,则运行 npm run eject 。在弹出工程化配置后,只需要关注 jestbable 两个配置。首先在根目录添加jest.config.jsbable.config.js 这两个文件,然后在 package.json 里把对应位置的配置迁移过来。(jest.config.js 按照配置说明重定义,疑问?)

// jest.config.js
const Config = {bail: 1,clearMocks: true,coverageProvider: 'v8',moduleFileExtensions: ['js', 'jsx', 'ts', 'tsx', 'json'],rootDir: './',setupFilesAfterEnv: ['<rootDir>/src/tests/setupTests.js'],testEnvironment: 'jsdom',testMatch: ['<rootDir>/src/**/__tests__/**/*.[jt]s?(x)'],testPathIgnorePatterns: ['\\\\node_modules\\\\',],moduleNameMapper: {"\\.(css|less|scss|sss|styl)$": "identity-obj-proxy"}};module.exports = Config;

创建tests 文件夹,添加 setupTests.js 文件,然后在 jest.config.js 添加 setupFilesAfterEnv 配置进行覆盖。

// setupTest.js
// 导入 extend-expect 目的是 rtl 的一些断言需要建立在这个库上,如 toBeInTheDocument
import '@testing-library/jest-dom/extend-expect';// jest.config.js
setupFilesAfterEnv: ['<rootDir>/src/tests/setupTests.js']

新建 components 文件夹,添加 HeaderList 两个组件。

Header: 在输入框中输入 toDo 内容,回车添加到 List 组件中。

List: 一个展示所有 ToDo 项的列表,可以进行点击编辑或删除。

(具体组件功能结构在此不一一陈列)

开展测试

需求分析

  • Header 组件

    • input 初始输入框为空
    • input 能输入内容
    • input 回车提交内容
    • input 提交后输入框置空
  • List 组件

    • 列表为空,右上角计数器值为 0

    • 列表不为空,右上角计数器存在且值为列表长度,列表项删除按钮存在,点击可将其删除

    • 列表不为空,点击列表项内容可将其修改,回车后保存修改后的内容

测试编写

在对应组件下添加 __tests__ 目录,创建格式如 *.test.[jt]s?(x) 的测试用例。

// header.test.tsx
import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react';import Header from '../index';let inputNode: HTMLInputElement;
const addUndoItem = jest.fn();
const inputEvent = {target: {value: 'new todo'}
}beforeEach(() => {render(<Header addUndoItem={addUndoItem} />);inputNode = screen.getByRole('textbox') as HTMLInputElement;
});describe('测试 Header 组件', () => {it('初始渲染时输入框为空', () => {expect(inputNode).toBeInTheDocument();expect(inputNode.value).toEqual('');})it('输入框能输入内容', () => {fireEvent.change(inputNode, inputEvent);expect(inputNode.value).toEqual('new todo');})it('输入框回车提交内容并将输入框置空', () => {fireEvent.change(inputNode, inputEvent);const enterEvent = {keyCode: 13}fireEvent.keyUp(inputNode, enterEvent);expect(addUndoItem).toHaveBeenCalledTimes(1);expect(inputNode.value).toEqual('');})
})// list.test.tsx
import React from 'react';
import { render, screen, fireEvent, within } from '@testing-library/react';import List, { IListProps } from '../index';const props: IListProps = {list: [{value: 'list item'}],deleteItem: jest.fn(),valueChange: jest.fn(),handleFinish: jest.fn()
}
let Counter: HTMLElement;
let listItemNode: HTMLElement[];beforeEach(() => {render(<List {...props}/>);listItemNode = screen.queryAllByRole('listitem') as HTMLElement[];
});describe('测试 List 组件', () => {it('初始渲染', () => {// 设置空白数据let {deleteItem,valueChange,handleFinish} = props;let blank = {list: [],deleteItem,valueChange,handleFinish}// 此处重新渲染从中取出 container 容器const { container } = render(<List {...blank}/>);const li = container.querySelector('li');// 初始渲染时列表项内容为空,计数器值为 0expect(li).toBeNull();Counter = screen.getByText(/0/i);expect(Counter).toBeInTheDocument();})it('删除列表项', () => {// 列表项不为空时,右上角计数器存在且值为列表长度expect(listItemNode).toHaveLength(1);Counter = screen.getByText(/1/i);expect(Counter).toBeInTheDocument();// 列表项删除按钮存在,点击将其删除let deleteBtn = listItemNode[0].querySelector('div') as HTMLElement;expect(deleteBtn).not.toBeNull();fireEvent.click(deleteBtn);expect(props.deleteItem).toHaveBeenCalledTimes(1);})it('编辑列表项', () => {// 点击列表项后可将其内容修改fireEvent.click(listItemNode[0]);let editInput = within(listItemNode[0]).getByRole('textbox') as HTMLInputElement;const editValue = {target: {value: 'edit todo'}}// 检查修改回调调用次数fireEvent.change(editInput, editValue);expect(props.valueChange).toHaveBeenCalledTimes(1);const keyUp = {keyCode: 13}// handleFinish 在输入框失去焦点和回车确认时触发事件fireEvent.keyUp(editInput, keyUp);// 注意,toHaveBeenCalledTimes 断言是记录历史调用次数,// 所有在这里想要判断 valueChange 不被调用,参数应该为 1 而不是 0expect(props.valueChange).toHaveBeenCalledTimes(1);// change 直接修改 input 的 value 值,不触发失焦事件expect(props.handleFinish).toHaveBeenCalledTimes(1);})
})

编写测试时需要注意的是,如果组件的更新动作是交由外部进行处理的,便不能期待进行某些操作后能得到与业务中一样的反馈,应该通过测试回调函数的响应的侧面验证其功能的可行性。

其他场景

异步操作

对于测试需要等待的响应事件或 Promise,使用 awaitthen 来进行处理。

// 场景一
// 点击按钮后异步更新其 textContent 文本,可以借助 findBy 异步查询 API 查找需要等待更新的元素
const button = screen.getByRole('button', {name: 'Click Me'})
fireEvent.click(button)
await screen.findByText('Clicked once')
fireEvent.click(button)
await screen.findByText('Clicked twice')// 场景二
// 需要等待回调函数的结果,使用 waitFor 对断言进行判断
await waitFor(() => expect(mockAPI).toHaveBeenCalledTimes(1))// 场景三
// 需要等待从 DOM 中删除元素
waitForElementToBeRemoved(document.querySelector('div.getOuttaHere')).then(() =>console.log('Element no longer in DOM'),
)

Rudux 测试

对于特别复杂的 redux,可以选择对其 reducereffect 使用基本的单元测试。更多场景下,对 redux connect 的组件使用 集成测试 。可以显式传递 mock store ,也可以使用 Redux Provider 包裹组件。

另一测试方案是单独测试 redux 和组件,单独导入未进行 connect 连接的组件,使用 Mock 方法模拟其 dispatch测试响应性。

官网案例

测试 Redux 连接的组件

// 建立带有 redux 测试的自定义 render, 后续测试集成组件便可以使用该自定义 render
// test-utils.jsx
import React from 'react'
import { render as rtlRender } from '@testing-library/react'
import { createStore } from 'redux'
import { Provider } from 'react-redux'
// Import your own reducer
import reducer from '../reducer'function render(ui,{initialState,store = createStore(reducer, initialState),...renderOptions} = {}
) {function Wrapper({ children }) {return <Provider store={store}>{children}</Provider>}return rtlRender(ui, { wrapper: Wrapper, ...renderOptions })
}// re-export everything
export * from '@testing-library/react'
// override render method
export { render }

问题收集

  • formatMessage not initialized yet, you should use it after react app mounted #2156.

    参考资料一

    参考资料二

  • Could not find required intl object. needs to exist in the component ancestry. Using default message as fallback

参考资料

Testing Library 官网文档

React Testing library 使用总结

Jest 官网文档

相关文章:

高质量前端之自动化测试

前端自动化测试&#xff1a;Testing Library 篇 引言 前端测试 静态测试 eslint、TypeScript 单元测试 jest、mocha 集成测试 enzyme、react-testing-library、mock 爬虫 前后端解耦 为什么要引入自动化测试 测试可以让开发者站在用户的角度考虑问题&#xff0c;通过测试的手…...

2023不伤人脉的全新商城分销,一劳永逸的消费分红

2023不伤人脉的全新商城分销&#xff0c;一劳永逸的消费分红 2023-02-24 11:52梦龙 2023不伤人脉的全新商城分销&#xff0c;一劳永逸的消费分红 如今是流量为王的时代&#xff0c;但是如何将流量转化为忠实客户是个难题。不再是单向的买卖关系&#xff0c;而是从对产品的关注…...

【代码随想录训练营】【Day21】第六章|二叉树|530.二叉搜索树的最小绝对差|501.二叉搜索树中的众数|236. 二叉树的最近公共祖先

二叉搜索树的最小绝对差 题目详细&#xff1a;LeetCode.530 这道题使我第一次了解到二叉树的双指针遍历法&#xff0c;详细可以先查看卡哥的讲解视频&#xff1a;《代码随想录 — 二叉搜索树中的众数》 利用二叉搜索树的特点&#xff1a; 中序遍历二叉搜索树得到一个有序序…...

leaflet 导出图片,打印图片(A4横版或竖版)

第093个 点击查看专栏目录 本示例的目的是介绍如何在vue+leaflet中打印图片导出图片。一个简单的leaflet插件示例,添加了一个图标来打印或导出地图。 直接复制下面的 vue+openlayers源代码,操作2分钟即可运行实现效果. 文章目录 示例效果配置方式示例源代码(共85行)安装插…...

Java面向对象:继承特性的学习

本文介绍了面向对象的继承特性: 什么是继承 继承的概念 Java中继承的语法 在继承下父类成员的访问 super和this关键字 父类和子类构造方法 在继承下类中出现初始化代码的执行顺序 父类成员的访问权限对子类的可见性 Java的继承关系 final关键字 认识继承和组合关系 继承特性的学…...

问答系统(QA)调研

引言 智能问答系统广泛用于回答人们以自然语言形式提出的问题&#xff0c;经典应用场景包括&#xff1a;智能语音交互、在线客服、知识获取、情感类聊天等。根据QA任务&#xff0c;可以将QA大致分为5大类&#xff0c;分别为&#xff1a; 文本问答&#xff08;text-based QA&am…...

商务租车的三大优势吸引企业以租代购

随着社会机经济的高速发展&#xff0c;租车模式的日益盛行&#xff0c;租车不仅仅是受个体户的青睐&#xff0c;而作为环保经济的出行方式也让越来越多的企业开始选择以租代买&#xff0c;据调查统计&#xff0c;最早开始商务租车的群体是外企。而近几年&#xff0c;国内的很多…...

蓝桥杯的比赛流程和必考点

蓝桥杯的比赛流程和必考点 距省赛仅1个多月&#xff01;蓝桥杯的比赛流程和必考点&#xff0c;你还不清楚&#xff1f; “巷子里的猫很自由&#xff0c;却没有归宿&#xff1b;围墙里的狗有归宿&#xff0c;终身都得低头。人生这道选择题&#xff0c;怎么选都会有遗憾。” 但不…...

【数据结构】红黑树

红黑树一、红黑树的概念二、红黑树的接口2.1 插入三、验证四、源码一、红黑树的概念 红黑树也是一个二叉搜索树&#xff0c;他是通过对任何一条从根到叶子的路径上各个结点着色方式的限制&#xff0c;最长路径长度不超过最短路径长度的 2 倍保持近似平衡。他在每个节点添加了一…...

从C++的角度理解C#的Event

由于技术背景是C起家的&#xff0c;所以对于C的概念很清楚&#xff0c;遇到C#的EVENT时候&#xff0c;总感觉这个概念比较抽象&#xff0c;不容易理解&#xff0c;但是当使用函数指针和回调函数来理解EVENT的时候&#xff0c;这个概念就清晰了。 首先对于EVENT来讲&#xff0c…...

商城进货记录交易-课后程序(JAVA基础案例教程-黑马程序员编著-第七章-课后作业)

【实验7-2】商城进货记录交易 【任务介绍】 1.任务描述 每个商城都需要进货&#xff0c;而这些进货记录整理起来很不方便&#xff0c;本案例要求编写一个商城进货记录交易的程序&#xff0c;使用字节流将商场的进货信息记录在本地的csv文件中。程序具体要求如下&#xff1a; …...

【正点原子FPGA连载】第十七章双核AMP实验 摘自【正点原子】DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南

1&#xff09;实验平台&#xff1a;正点原子MPSoC开发板 2&#xff09;平台购买地址&#xff1a;https://detail.tmall.com/item.htm?id692450874670 3&#xff09;全套实验源码手册视频下载地址&#xff1a; http://www.openedv.com/thread-340252-1-1.html 第十七章双核AMP…...

内存管理框架---页(一)

文章目录物理内存的模型非一致内存访问--NUMA一致内存访问模型--UMA内存管理架构页页框管理页描述符页描述符字段flags字段详解gfp_mask 标志获得页alloc_pages__get_free_pages获得填充为0的页释放页kmallocvmalloc参考资料你用心写的每一篇文章&#xff0c;可能会带别人和自己…...

华为OD机试真题Python实现【流水线】真题+解题思路+代码(20222023)

流水线 题目 一个工厂有m条流水线 来并行完成n个独立的作业 该工厂设置了一个调度系统 在安排作业时,总是优先执行处理时间最短的作业 现给定流水线个数m 需要完成的作业数n 每个作业的处理时间分别为 t1,t2...tn 请你编程计算处理完所有作业的耗时为多少 当n > m时 首先…...

「JVM 编译优化」Graal 编译器

文章目录1. 历史背景2. 构建编译调试环境3. JVMCI 编译器接口4. 代码中间表示5. 代码优化与生成1. 历史背景 Graal 编译器在 JDK 9 以 Jaotc 提前编译工具的形式首次加入到官方的 JDK 中&#xff0c;JDK 10 开始提供替换&#xff08;得益于 HotSpot 编译器接口&#xff0c;Jav…...

蓝牙标签操作指南

一、APP安装指南 1.APP权限问题 电子标签APP安装之后&#xff0c;会提示一些权限的申请&#xff0c;点击允许。否则某些会影响APP的正常运行。安装后&#xff0c;搜索不到蓝牙标签&#xff0c;可以关闭App&#xff0c;重新打开。 2.手机功能 运行APP时候&#xff0c;需要打开…...

嵌入式 Linux Shell编程

目录 1、shell脚本 2、执行shell脚本 3、shell脚本编写 3.1 shell变量 3.2 标准变量或环境变量 3.4 变量赋值有五种格式 3.5 运算符和表达式 关系运算符 布尔运算符 3.6 Test命令用法 1、判断表达式 2、判断字符串 3.判断整数 4、判断文件 3.7 数组 1、数组定义…...

Web前端学习:一

编辑器的基础使用 编辑器推荐使用&#xff1a; HBuilderx&#xff08;免费中文&#xff09;&#xff08;建议使用&#xff09; Sublime&#xff08;免费英文&#xff09; Sublime中文设置方法&#xff0c;下载语言插件&#xff1a; 1、进入Sublime后&#xff0c;ShiftCtrlP…...

SpringBoot集成Redis实现分布式会话

在单体应用的时代&#xff0c;Session 会话直接保存在服务器中&#xff0c;实现非常简单&#xff0c;但是随着微服务的流行&#xff0c;现代应用架构基本都是分布式架构&#xff0c;请求随机的分配到后端的多个应用中&#xff0c;此时session就需要共享&#xff0c;而存储在red…...

2023年关于身份安全的4 个预测

如果您身处技术领域&#xff0c;就会知道现在是时候盘点过去的一年&#xff0c;展望未来 365 天将影响业务、创新以及我们工作方式的因素的季节。这不是一门精确的科学&#xff0c;我们也不总是对的。但是推测很有趣&#xff0c;当我们看到其中一些趋势成为现实时会更有趣。本文…...

Linux期末考试应急

Linux期末考试应急 虚拟机添加硬盘、分区、格式化、挂载、卸载 fdisk -l#查看系统现有分区fdisk <指定磁盘>#指定磁盘分区sudo mkfs.ext3 <指定分区>#格式化磁盘###挂载磁盘1.新建一个目录sudo mkdir /mnt/test2.将指定分区挂载到对应目录sudo mount /dev/sdb10 /…...

mars3d对geojson图层分属性设置样式

开发中可能会遇到如下需求&#xff0c;在全省的数据中按某个属性⾼亮展示某市区。此时就需要使⽤分属性样式的api了。⽂档如下。GeoJsonLayer - Mars3D API文档属性是根据⽮量数据的属性进⾏匹配。可以通过 layer.graphics[0]?.attr ⽅式获取。 指导有哪些属性之后先设置…...

三、锁相关知识

文章目录锁的分类可重入锁、不可重入锁乐观锁、悲观锁公平锁、非公平锁互斥锁、共享锁深入synchronized类锁、对象锁synchronized的优化synchronized实现原理synchronized的锁升级重量锁底层ObjectMonitor深入ReentrantLockReentrantLock和synchronized的区别AQS概述加锁流程源…...

C语言数据类型

C 数据类型 在 C 语言中&#xff0c;数据类型指的是用于声明不同类型的变量或函数的一个广泛的系统。变量的类型决定了变量存储占用的空间&#xff0c;以及如何解释存储的位模式。 C 中的类型可分为以下几种&#xff1a; 1 基本类型&#xff1a; 它们是算术类型&#xff0c;…...

华为OD机试真题Python实现【水仙花数】真题+解题思路+代码(20222023)

水仙花数 题目 所谓的水仙花数是指一个n位的正整数其各位数字的n次方的和等于该数本身, 例如153 = 1^3 + 5^3 + 3^3,153是一个三位数 🔥🔥🔥🔥🔥👉👉👉👉👉👉 华为OD机试(Python)真题目录汇总 输入 第一行输入一个整数N, 表示 N 位的正整数 N 在3…...

【华为OD机试模拟题】用 C++ 实现 - 非严格递增连续数字序列(2023.Q1)

最近更新的博客 华为OD机试 - 入栈出栈(C++) | 附带编码思路 【2023】 华为OD机试 - 箱子之形摆放(C++) | 附带编码思路 【2023】 华为OD机试 - 简易内存池 2(C++) | 附带编码思路 【2023】 华为OD机试 - 第 N 个排列(C++) | 附带编码思路 【2023】 华为OD机试 - 考古…...

RN面试题

RN面试题1.React Native相对于原生的ios和Android有哪些优势&#xff1f;1.性能媲美原生APP 2.使用JavaScript编码&#xff0c;只要学习这一种语言 3.绝大部分代码安卓和IOS都能共用 4.组件式开发&#xff0c;代码重用性很高 5.跟编写网页一般&#xff0c;修改代码后即可自动刷…...

【数据存储】浮点型在内存中的存储

目录 一、存储现象 二、IEEE标准规范 1.存储 2.读取 三、举例验证 1.存储 2.读取 浮点型存储的标准是IEEE&#xff08;电气电子工程师学会&#xff09;754制定的。 一、存储现象 浮点数由于其有小数点的特殊性&#xff0c;有很多浮点数是不能精确存储的&#xff0c;如&#…...

Servlet笔记(8):异常处理

1、错误页面配置 web.xml <!-- servlet 定义 --> <servlet><servlet-name>ErrorHandler</servlet-name><servlet-class>ErrorHandler</servlet-class> </servlet> <!-- servlet 映射 --> <servlet-mapping><servle…...

stm32f407探索者开发板(二十一)——窗口看门狗

文章目录一、窗口看门狗概述1.1 看门狗框图1.2 窗口看门狗工作过程总结1.3 超时时间1.4 为什么需要窗口看门狗1.5 其他注意事项二、常用寄存器和库函数2.1 控制寄存器WWDG_ CR2.2 配置寄存器WWDG_ CFR2.3 状态寄存器WWDG_SR三、手写窗口看门狗3.1 配置过程3.2 初始化窗口看门狗…...