# 什么是 QA 工程师

QA(Quality Assurance)是质量保证的意思。但实际上好像每家公司对 QA 的定义和岗位职责不太一样,具体可以参考下知乎上的这篇文章:QA 这个职位主要做什么?重要么? (opens new window)

# 测试风格

测试驱动开发(Test-Driven Development,TDD)、行为驱动开发(Behavior Driven Development,BDD)均是敏捷开发方法论。

  • TDD 的核心思想是在编写实际代码之前先编写测试代码。开发者首先定义一个需要实现的功能,并为其编写测试用例。然后,他们编写足够的代码使测试通过,最后进行重构以优化代码结构。简单来说就是先写测试用例,再干活

  • BDD 强调以业务需求和行为为中心,将开发者、测试人员和非技术人员的语言整合到一个统一的开发过程中。它关注系统行为的描述和实现,而不仅仅是代码的测试。简单来说就是先干活,再写测试用例,国内大部分公司都是采用这种方式。

# 测试金字塔

保持合理的测试⽐例:⼤量的单元测试、⼀些集成测试和少量的端到端测试。

# 冒烟测试

前端冒烟测试是指在进行软件开发或者系统集成后,通过执行一组基本的测试用例来验证应用程序的主要功能是否能够正常工作

冒烟测试的目的是尽早地发现潜在的重大问题,确保系统的基本功能没有严重的缺陷。

这些测试通常是由开发团队或者测试团队在每次构建、集成或者部署之后执行的。

1. 测试内容

  • 页面加载:验证主要页面是否能够正确加载,包括页面元素、样式和脚本。

  • 基本导航:测试用户能否正常导航到应用程序的不同页面,确保核心导航功能可用。

  • 关键操作:针对应用程序的核心功能执行关键操作,例如提交表单、点击按钮等。

  • 数据加载:验证应用程序是否能够正确加载和显示关键数据。

  • 错误处理:测试应用程序在遇到错误时的行为,确保用户获得清晰的错误信息并且系统不会崩溃。

2. 执行时机

  • 代码提交后:在每次代码提交后执行冒烟测试,以确保新的代码变更没有破坏系统的主要功能。

  • 构建和部署后:在构建和部署过程完成后,执行冒烟测试,确保构建的应用程序能够正常工作。

  • 集成测试前:在进行更全面的集成测试之前,执行冒烟测试,以排除系统的基本问题。

3. 测试自动化

  • 测试脚本编写:冒烟测试可以使用自动化测试框架编写测试脚本,以实现快速执行和自动反馈。

  • 持续集成:将冒烟测试集成到持续集成(CI)工具中,以在每次构建或者部署后自动执行。

  • 自动化工具:使用自动化测试工具如 Cypress (opens new window)Selenium (opens new window) 等来支持冒烟测试的自动执行。

4. 好处

  • 快速反馈:冒烟测试可以迅速发现主要功能的问题,提供快速的反馈。

  • 阻止严重问题:通过及早发现潜在的重大问题,避免在后续开发阶段或者交付后才发现。

  • 降低成本:在早期发现和修复问题可以降低问题修复的成本,避免问题扩大化。

  • 提高信心:通过冒烟测试的通过,开发团队和测试团队可以更有信心地进行后续的测试和开发工作。

# 单元测试

1. 定义

前端单元测试是对应用程序中最小可测试单元的测试。这通常是函数、方法或者组件。

2. 工具

补充

Macaca (opens new window) 是一套面向用户端软件的测试解决方案,提供了自动化驱动,环境配套,周边工具,集成方案,旨在解决终端上的测试、自动化、性能等方面的问题。

3. 测试目标

单元测试主要关注函数、方法、组件等独立单元的行为,以确保它们在不同情况下都能正确地执行。

4. 测试内容

  • 输入输出测试:针对函数或方法,测试给定输入时是否能够得到预期的输出。

  • 边界条件测试:测试在输入达到边界值时,程序是否能够正确处理。

  • 异常情况测试:测试代码在面对异常或错误输入时的行为。

5. 示例代码

// 使用 Jest 进行单元测试
// 测试函数 add 的行为
function add(a, b) {
  return a + b;
}

test("adds 1 + 2 to equal 3", () => {
  expect(add(1, 2)).toBe(3);
});
1
2
3
4
5
6
7
8
9

# 前端如何做单元测试

前端单元测试是软件测试中的⼀个重要环节,它涉及到对前端应⽤中的最⼩可测试部分(如函数、组件等)进⾏独⽴测试以确保它们按预期⼯作。前端单元测试通常使⽤特定的测试框架和库来执⾏。

以下是前端单元测试的⼀些关键步骤和常⽤⼯具:

# 关键步骤

1. 选择测试框架

选择⼀个适合项⽬需求的前端测试框架,如 Jest (opens new window)Mocha (opens new window)Jasmine (opens new window) 等。

2. 编写测试⽤例

对每个函数或组件编写测试⽤例,包括测试它们在各种输⼊下的预期输出。

3. 模拟依赖

使⽤如 Sinon.js (opens new window)jest.mock (opens new window) 等⼯具来模拟外部依赖,以确保测试的独⽴性。

4. 运⾏和监控测试

使⽤测试框架提供的命令⾏⼯具来运⾏测试。⼤多数框架都⽀持监控模式,即在⽂件更改时⾃动重新运⾏测试。

5. 断⾔

使⽤断⾔库(许多测试框架已内置)来验证代码的⾏为符合预期。

6. 集成到开发流程

将单元测试集成到持续集成/持续部署(CI/CD)流程中。

7. 覆盖率报告

⽣成测试覆盖率报告,以评估测试的⼴泛性和有效性。

8. UI 还原度报告

基于 UI 设计师和前端开发设计的⻚⾯进⾏深度对⽐。

# 常用工具

1. Jest

Jest (opens new window) 是⼀个流⾏的 JavaScript 测试框架,提供了丰富的特性,如快照测试、易于配置等。

2. Mocha

Mocha (opens new window) 是另⼀个⼴泛使⽤的测试框架,通常与 Chai(断⾔库)和 Sinon(⽤于模拟)⼀起使⽤。

3. Jasmine

Jasmine (opens new window) 是⼀个⾏为驱动开发(BDD)的测试框架,它内置了断⾔库。

4. Enzyme / @testing-library/react

对于 React 应⽤,Enzyme (opens new window)React Testing Library (opens new window) 提供了组件级别的测试⼯具。

5. Vue Test Utils

Vue Test Utils (opens new window) 是 Vue.js 官⽅的单元测试实⽤库。

6. Karma

Karma (opens new window) 是⼀个测试运⾏器,可以让你在多个真实浏览器上运⾏测试。

7. BackStop.js

BackstopJS (opens new window) 是⼀个流⾏的、⽤于⾃动化视觉回归测试的⼯具。它主要⽤于前端项⽬中,以确保 UI 的⼀致性和稳定性,特别是在进⾏重构或添加新特性时。

# 自动化测试

1. 定义

前端自动化测试是通过脚本自动运行测试用例,而不需要手动进行测试操作的一种测试方式。

2. 工具

3. 测试目标

自动化测试主要关注整个应用程序或系统的功能,模拟用户的操作流程,确保整个应用在各种情况下都能够正常工作。

4. 测试内容

  • 用户交互测试:模拟用户在应用程序中的交互,检查页面元素、点击按钮等。

  • 集成测试:测试不同组件或模块之间的集成,确保它们能够正确协同工作。

  • 性能测试:测试应用程序在不同负载下的性能表现。

5. 示例代码

// 在 Cypress 中编写测试脚本
describe("My First Test", () => {
  it("Does not do much!", () => {
    // 打开网页
    cy.visit("https://example.cypress.io");

    // 点击链接
    cy.contains("type").click();

    // 输入文本
    cy.url().should("include", "/commands/actions");
  });
});
1
2
3
4
5
6
7
8
9
10
11
12
13

# 接口测试

1. 定义

前端接口测试是验证前端应用程序与后端服务之间的接口是否按照设计规范进行交互的一种测试。

2. 工具

3. 测试目标

确保前端应用程序与后端服务之间的数据传输和交互是正确的,接口文档是否符合实际实现。

4. 测试内容

  • 请求和响应测试:验证前端发送的请求和后端返回的响应是否符合接口文档规范。

  • 错误处理测试:测试接口在异常情况下是否能够正确返回适当的错误码和信息。

  • 性能测试:针对接口的性能进行测试,例如响应时间、并发请求等。

5. 示例代码

// 使用 Jest 编写接口测试
const axios = require("axios");

test("fetch user data from API", async () => {
  const response = await axios.get("https://api.example.com/user/1");
  const userData = response.data;

  expect(userData.id).toBe(1);
  expect(userData.name).toBe("John Doe");
});
1
2
3
4
5
6
7
8
9
10

# 端到端(E2E)测试

1. 定义

端到端测试是对整个应用程序进行测试,模拟用户的实际操作流程,验证应用程序在真实环境中的运行情况。

2. 工具

3. 测试目标

确保整个应用程序在各种情况下都能够正常工作,包括用户界面、功能和与后端服务的交互。

4. 测试内容

  • 用户交互测试:模拟用户在应用程序中的交互,包括点击、输入文本、提交表单等。

  • 页面元素测试:验证页面上的元素是否正确显示,并且是否响应用户的操作。

  • 集成测试:测试不同组件或模块之间的集成,确保它们能够正确协同工作。

5. 示例代码

// 在 Cypress 中编写端到端测试脚本
describe("My First E2E Test", () => {
  it("Visits the app", () => {
    // 打开应用程序
    cy.visit("https://example.cypress.io");

    // 点击链接
    cy.contains("type").click();

    // 输入文本
    cy.url().should("include", "/commands/actions");
  });
});
1
2
3
4
5
6
7
8
9
10
11
12
13

# 性能测试

1. 基准测试

面向切面编程 AOP 无侵入式统计。

Benchmark 基准测试方法,它并不是简单地统计执行多少次测试代码后对比时间,它对测试有着严密的抽样过程。执行多少次取决于采样到的数据能否完成统计。根据统计次数计算方差。

wrk (opens new window) 是一款现代 HTTP 基准测试工具。而 wrk2 (opens new window) 是基于 wrk 的 HTTP 基准测试工具。

2. 压力测试

对网络接口做压力测试需要检查的几个常用指标有吞吐率响应时间并发数,这些指标反映了服务器并发处理能力。

PV 是网站当日访问人数,UV 是独立访问人数。PV 每天几十万甚至上百万就需要考虑压力测试。换算公式:QPS = PV / t。PS:1000000 / 10 _ 60 _ 60 = 27.7(100 万请求集中在 10 小时,服务器每秒处理 27.7 个业务请求)。

常用的压力测试工具有:apache 自带的压力测试工具 ab (opens new window)siege (opens new window)http_load (opens new window)

ab -c 100 -n 100 http:localhost:8081 每秒持续发出 28 个请求

Request per second 表示服务器每秒处理请求数,即为 QPS;

Failed requests 表示此次请求失败的请求数;

Connection Times 连接时间,它包括客户端向服务器端建立连接、服务器端处理请求、等待报文响应的过程。

# JsLint & JsHint

  • 目的:检测 JavaScript 代码标准。

  • 原因:JavaScript 代码诡异,保证团队代码规范。

  • lint (opens new window)

  • hint (opens new window)

  • 搭配自动化管理工具完善自动化测试 gtunt-jslint、grunt-jshint。

# 自动化测试实战

  1. 一般测试用例文件都是以 .test.js、.spec.js 后缀命名的,也有的直接写成 indexSpec.js。并且测试用例文件和它对应的 js 文件名字也是一一对应的,比如 index.spec.js 就是 index.js 这个文件的测试用例文件。
// index.js
window.add = function(a) {
  return (a = a + 1);
};
1
2
3
4
// index.spec.js
describe("函数基本测试用例", function() {
  it("+1测试函数", function() {
    expect(window.add(1)).toBe(2);
  });
});
1
2
3
4
5
6
  1. 执行 npm init -y 初始化 package.json 文件。

  2. 安装 karma (opens new window)

安装方式首选 npm:

npm install karma --save-dev
1

如果 npm 实在太慢可以用 yarn,会比 npm 快一点:

yarn add karma
1

用 npm 安装的话会生成 package-lock.json 文件,用 yarn 安装的话会生成 yarn.lock 文件。

如果想全局安装 karma,还是需要先执行以上命名在本地安装好 karma,然后再执行以下命令安装 karma-cli (mac 系统下要加 sudo,不然没权限):

sudo npm install -g karma-cli
1

或者用 yarn:

yarn add global karma-cli
1

全局安装完成后,就可以使用以下命令在本地配置 karma 的环境,这样就不需要在 package.json 手动添加命令了。

karma init
1

karma

配置完之后就可以在本地看到一个 karma.conf.js 文件了。这个文件需要重视,因为它就是 karma 的配置文件。

karma

karma

  1. 在 package.json 的 scripts 中添加命令
scripts: {
  "init": "karma init"
}
1
2
3

如果是全局安装了 karma,这一步就不需要了。

  1. 学会使用无头浏览器

无头浏览器就是看不见界面但是确实存在的浏览器。PhantomJS (opens new window) 就是一个早期的无头浏览器,已经很老了,跟它差不多名字的还有一个叫作 PhantomCSS,前者管 js,后者管 css。

  1. 新建一个 src 目录,把 index.js 移动到 src 目录下。然后在 karma 配置文件中配置:
// 需要进行测试的文件
files: [
  "./src/**/*.js",
  "./tests/unit/**/*.spec.js"
],
1
2
3
4
5
  1. 再设置 singleRun 为 true
singleRun: true,
1
  1. 接下来还需要安装以下包:
npm install karma-jasmine jasmine-core --save-dev
1
  1. 在 package.json 中添加脚本命令:
scripts: {
  "test": "karma start"
}
1
2
3

然后就可以运行 npm test 命令启动测试流程了。然而报错了:

karma

查看报错信息就会发现,原来是没找到 PhantomJS 这个无头浏览器,官网之所以没有提及这个,是因为它一开始就装了 chorme。

karma

  1. 安装 PhantomJS (opens new window),这个包太老了,npm 上都不维护了。
npm install phantomjs --save-dev
1

但是下载过程太慢了,没办法换成 cnpm 来下载。

cnpm install phantomjs --save-dev
1

安装完成后重新运行 npm test,发现还是不行,报了同样的问题。所以我们还得再安装一个适配器 karma-phantomjs-launcher。

npm install karma-phantomjs-launcher --save-dev
1

重新运行 npm test 就可以看到启动成功了。

karma

图里展示我们的测试用例也测试通过了。

此时如果把测试用例改成:

// index.spec.js
describe("函数基本测试用例", function() {
  it("+1测试函数", function() {
    expect(window.add(1)).toBe(3);
  });
});
1
2
3
4
5
6

就会发现测试不通过了。

karma

  1. 查看测试覆盖率

接下来把 index.js 代码改一下

// index.js
window.add = function(a) {
  if (a == 1) {
    return 1;
  } else {
    return (a = a + 1);
  }
};
1
2
3
4
5
6
7
8

相应的,index.spec.js 也得改:

describe("函数基本测试用例", function() {
  it("+1测试函数", function() {
    expect(window.add(1)).toBe(1);
    expect(window.add(1)).toBe(2);
  });
});
1
2
3
4
5
6

因为代码里有 if ... else 分支,所以测试用例需要写两个 expect,如果只写一个的话,就会导致测试覆盖率不全。

那么,如何查看测试覆盖率呢?

(1)新建一个 docs 文件夹,用来生成测试报告给别人看。

(2)安装测试覆盖率检查工具 karma-coverage (opens new window)

npm install karma-coverage --save-dev
1

(3)安装完成后,需要改下 karma 的配置文件:

preprocessors: {
  // 测试哪些文件对应的覆盖率
  'src/**/*.js': ['coverage']
},

reporters: ['progress', 'coverage'],

// 报表生成的位置
coverageReporter: {
  type: 'html',
  dir: 'docs/coverage/'
},
1
2
3
4
5
6
7
8
9
10
11
12

(4)重新运行 npm test,就会看到 docs 目录下生成了一些文件。

karma

(5)打开 index.html,就可以在浏览器中看到测试报告。覆盖率是通过分支语句计算出来的。

karma

点击 index.js 还能看到具体是哪行代码测试不通过。

karma

(6)把 index.js 再改下:

describe("函数基本测试用例", function() {
  it("+1测试函数", function() {
    expect(window.add(1)).toBe(1);
    expect(window.add(2)).toBe(3);
  });
});
1
2
3
4
5
6

重新运行就可以看到所有测试用例通过了。

karma

  1. UI 自动化测试

(1)一个比较好用的库:backstopjs (opens new window)

(2)全局安装

sudo npm install -g backstopjs
1

npm 安装实在是太慢了,没办法只能用 cnpm

sudo cnpm install -g backstopjs
1

(3)安装好之后运行以下命令,会生成 backstop 的配置文件 backstop.json 和 backstop_data 文件夹。

backstop init
1

karma

(4)把 backstop.json 文件中的 id 和 scenarios 对象中的 url 改成腾讯地图:

"id": "qq"

"scenarios": [
  {
    "url": "https://map.qq.com/m/"
  }
]
1
2
3
4
5
6
7

(5)puppeteer

puppeteer (opens new window) 是当下最新的无头浏览器,生态也比较广。

puppet 文件夹下的东西就是用来操作这个的。puppet (opens new window) 也是一个库。

karma

(6)运行以下命令:

backstop test
1

执行完成后会自动在浏览器中弹出以下页面:

karma

这个时候可以发现 backstop_data 文件夹下多出了两个文件夹。

karma

其中,bitmaps_test 文件夹存放的就是你的页面,页面的尺寸就是跟 backstop.json 里面设置的尺寸一样的。

karma

而 html_report 文件夹存放的是生成的一些报表。

(7)刚刚执行命令的时候还会发现报错了,说没有找到图片。

karma

没找到是因为这里的路径没找到。

karma

(8)直接在 backstop_data 文件夹中新建一个 bitmaps_reference 文件夹。然后让 UI 把新的图放这个文件夹里边。同时删掉 bitmaps_test 文件夹。

karma

(9)重新执行 backstop test 命令,就能看到自己的页面跟 UI 设计的图哪些地方不一样了。

karma

karma

(10)由于生成的报表很重要,接下来我们把生成报表的路径改一下。然后重新执行命令就可以了,就会在 docs 生成一个 html_report 目录。

"paths": {
  "bitmaps_reference": "backstop_data/bitmaps_reference",
  "bitmaps_test": "backstop_data/bitmaps_test",
  "engine_scripts": "backstop_data/engine_scripts",
  "html_report": "docs/html_report", // 修改报表生成位置
  "ci_report": "backstop_data/ci_report"
}
1
2
3
4
5
6
7

(11)先把上面的基本流程练熟了之后,接下来就是要去熟悉操作 puppet 里面的东西了。

# e2e 测试实战

  1. 在 tests 目录下新建 e2e 文件夹。

  2. e2e 测试最流行的框架是 selenium-webdriver (opens new window),同时它也是学习自动化测试最关键的一个库。

(1)安装这个库

npm install selenium-webdriver
1

(2)安装驱动,安装推荐的任意一个都可以

selenium-webdriver

(3)这里选择安装 FireFox 的驱动。

selenium-webdriver

(4)下载完成后解压缩,然后把 geckodriver 放到项目的根目录下。

selenium-webdriver

(5)接着在 e2e 目录下新建一个 baidu.spec.js 文件,然后到 selenium-webdriver 的 npm 上复制下面这段代码到这个文件中,地址改成百度的地址。

const { Builder, By, Key, until } = require("selenium-webdriver");

(async function example() {
  let driver = await new Builder().forBrowser("firefox").build();
  try {
    await driver.get("https://www.baidu.com/");
    await driver.findElement(By.name("wd")).sendKeys("深圳大学", Key.RETURN);
    await driver.wait(until.titleIs("深圳大学_百度搜索"), 1000);
  } finally {
    await driver.quit();
  }
})();
1
2
3
4
5
6
7
8
9
10
11
12

(6)接着执行以下命令

node ./tests/e2e/baidu.spec.js
1

但是出现了以下问题。

selenium-webdriver

想了一下,猜测可能是因为我没有安装火狐浏览器,于是下载安装了一个。然后重新运行命令,就发现成功了。命令执行完成后会自动打开火狐浏览器,百度搜索深圳大学,然后再关闭,说明端到端测试完成了。这就是最简单的 e2e 测试。

  1. Nightwatch.js (opens new window) 是另一个端到端测试框架,写起来很爽,但是配置起来太复杂、太难了。

  2. Cypress (opens new window) 也是一个端到端测试框架,这个就以配置简单闻名,而且有中文文档。

  3. Rize.js (opens new window) 是一个提供顶层的、流畅并且可以链式调用的 API 的库,它能让您简单地使用 puppeteer。它的 api 很少,但是却够用。puppeteer 本身是基于 Chrome 的,它的不足就是只能测 Chrome,不能测其他浏览器。

(1)执行以下命令安装 puppeteer 和 rize。

npm install --save-dev puppeteer rize
1

npm 安装太慢了,最后使用 cnpm 安装。

cnpm install --save-dev puppeteer rize
1

(2)安装完成后,在 e2e 文件夹下新建 github.spec.js 文件,并写入以下代码。

const Rize = require("rize");
const rize = new Rize();

rize
  .goto("https://github.com/")
  .type("input.header-search-input", "node")
  .press("Enter")
  .waitForNavigation()
  .assertSee("Node.js")
  .end(); // 别忘了调用 `end` 方法来退出浏览器!
1
2
3
4
5
6
7
8
9
10

(3)执行以下命令:

node ./tests/e2e/github.spec.js
1

执行命令后需要等待一下,因为是无头浏览器,所以什么东西也看不到,只能等。如果执行成功,就会自动退出命令,如果执行不成功,就会报错。

  1. Jest (opens new window) 是比较全面的一个测试框架,什么都能做,还是希望能够多去使用下,因为后面很多项目实战都会用到。如果项目是 react,那测试框架就选这个了。如果是 vue,就选 cypress。唯一的弱点就是不适合做异步。

  2. mocha + chai 和 Jest 是 vue 项目推荐的两套单元测试框架。Cypress 和 Nightwatch 是 vue 项目推荐的两套端到端测试框架。

# 接口测试实战

mocha (opens new window) 主要用来做接口测试,因为它适合做异步测试。下面就来使用下它。

  1. 在 tests 目录下新建 service 文件夹,并建一个 app.js 文件。从 koa (opens new window) 官网上复制一段代码放到文件中。
const Koa = require("koa");
const app = new Koa();

app.use(async (ctx) => {
  ctx.body = {
    data: "深圳大学"
  };
});

app.listen(3000, () => {
  console.log("服务启动成功");
});
1
2
3
4
5
6
7
8
9
10
11
12
  1. 接着安装 koa
npm install koa --save-dev
1
  1. 安装好之后执行
node ./tests/service/app.js
1

然后浏览器中访问 localhost:3000 就能看到接口返回的 json。

  1. 在 service 目录下再建一个 app.spec.js 文件。
const superagent = require("supertest");
const app = require("./app");

function request() {
  return superagent(app.listen());
}

describe("NodeUii 自动化脚本", function() {
  it("获取后台接口数据", function(done) {
    request()
      .get("/")
      .set("Accept", "application/json")
      .expect("Content-Type", /json/)
      .expect(200)
      .end(function(err, res) {
        if (err) {
          done(new Error("请求出错!"));
        } else {
          if (res.body.data === "深圳大学") {
            done();
          } else {
            done(new Error("接口数据出错!"));
          }
        }
      });
  });
  it("404容错脚本", function(done) {
    request()
      .get("/user?")
      .expect(404, done);
  });
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

然后 app.js 中要加一句 module.exports = app;,同时还要安装 superagent、mocha 和 mochawesome:

npm install superagent mocha mochawesome --save-dev
1
  1. 接着在根目录下创建一个 mochaRunner.js 文件。
const Mocha = require("mocha");

const mocha = new Mocha({
  reporter: "mochawesome",
  reporterOptions: {
    reportDir: "docs/mochawesome-report"
  }
});

mocha.addFile("./tests/service/app.spec.js");

mocha.run(function() {
  process.exit(0);
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14

此时发现不应该安装 superagent,而是要安装 supertest:

npm install supertest --save-dev
1
  1. 接着运行以下命令
node mochaRunner.js
1

但是此时报错了

mocha

检查发现是 res.body.data 而不是 res.body。重新运行就可以了。

mocha

可以看到有 1 个用例成功了,1 个用例失败了,此时在 docs 目录下同样能看到生成的文件。

mocha

打开 mochawesome.html 文件,就可以看到性能测试报告了。

mocha

# QA 学习概览

qa

上次更新时间: 2023年12月27日 17:01:16