C++测开,自动化测试,业务(第一段实习)
目录
🌼前言
一,实习经历怎么写简历
🌹业务理解
🎂结构化表达
二,实习
🦂技术和流程卡点
🔑实习收获 / 代码风格
三,测试理论,用例设计,工具链
🌳测试理论 / 用例设计
🍌工作内容
四,交互式 / 非交互式自动化测试实践
📱交互式自动化测试
👖非交互式自动化测试
📕手动确定覆盖率
五,测试工程构建 / 主要业务
🐎测试工程构建
🐘主要业务(API 的二次开发)
概念
客户如何工作
API 和 平台的关系
六,测试分级 / bug 排查 / 单元测试规范
🐱测试用例分级
🐉BUG排查 / 定位
🐒测试模式
🏢单元测试规范
🍑优秀的单元测试
🌼单元测试细节 / 项目单测流程
七,代码质量问题案例
🌊案例
🌼POD 对象
🌼前言
3个月的实习,之前就面了 15 分钟,闲聊 5 分钟,隔天发的 offer
毕竟只是任务重,招点实习生来打杂,以下总结下实习经验
一,实习经历怎么写简历
🌹业务理解
①
业务全套首先要弄清楚,因为面试官不了解这个行业,你能把前前后后的产品逻辑讲清楚,他就觉得你学习能力还不错
②
结合公司文档,将公司文档中,一些和自己平时工作有关联的东西,加以记录和理解
(不是在内网拷贝,而且用自己话,记录下来)
后面面试时,能讲清楚逻辑和细节,就行
比如 RAII 机制的一些宏,具体怎么实现的,涉及 C++11 的哪些特性,解决了什么问题等等
③
实习中,一整套的工作流程是怎样的,有什么比较热门的技术,在日常的功能测试,交互式,非交互式测试中使用了
④
或者,你平时测试的那些 API 接口,在整个业务的上下游,整天的链路逻辑,你处于哪个位置等
⑤
实习的一些收获,比如干活前,需要明确需求和 ddl,防止返工,大胆问
或者怎么处理分支冲突,以及 Git Extension, GitLab 的使用,还有宏失败了怎么处理
🎂结构化表达
①
实习经历分:工作职责 / 技术和框架 / 实习收获,三个方面去写
②
比如使用 C++ Catch2 测试框架,GenCMake 自动添加 CMake,.mac 宏录制和自动化测试跑宏,遇到一个新的接口怎么区测试的完整流程
③
实习做的东西,比如写了 100+ 不同类型接口的测试用例,完成 30 个接口覆盖率的提升,发现 9 个接口 BUG,3 天时间熟悉产品的基本操作
二,实习
🦂技术和流程卡点
①
技术上,最开始的宏录制,容易失败,因为存在无关操作,比如点击了无关的点,缩放了屏幕大小,覆盖已保存的文件;
或者,没有区分不同类型的变量,误判了父类型和子类型等等
②
测试流程上,不熟悉整个从 VS 检索接口注释,到平台尝试复现 API 功能,再到 VScode 搜索历史代码的整个流程
③
出现分支冲突,不知道如何处理,比如常见的 commit, pull merge, fetch all,以及 stash, stash pop 等;
分支冲突时可以注释他人的代码;或者询问 mentor 谁修改了这个文件
④
部门沟通上,你要和产品,测试,开发进行对接,需要频繁的沟通,以保证自己的工作效率(比如人均 3 次 / 天)
某个功能点,产品比你了解的更清楚;某个接口的具体实现,问开发也是最好的选择
比如接口返回值,位于平台哪里,函数调用需要注意的点,某些没跑进覆盖率的代码实现
⑤ 遇到困惑的点,积极跟 mentor 沟通,不要闭门造车
🔑实习收获 / 代码风格
①
技术上,熟悉了 Catch2 的部分操作,学会处理分支冲突,宏失败怎么排查,以及记录遇到的问题避免重复提问,还有 P1,P2 功能性+无效等价类+边界+异常场景的测试用例怎么写
②
代码风格上,首先,避免使用 new / delete, malloc / free 这一套,容易忘记释放导致内存泄露,而应该使用 C_AUTO 类宏,来声明指针,数组,句柄,锁等;
但是部分接口,需要接收已有的地址,就不能用 C_AUTO 宏来初始化,而应该使用 C++ 自带的初始化,比如 svxDeskFace deskFace{};
③
频繁被调用的代码,可以封装为函数,放到公用的头文件里
④
代码中,注释不要太多,但是基本的接下来上百行代码,别人知道你在干什么就行,比如 Catch2 的 WHEN("niu bi plus")
三,测试理论,用例设计,工具链
🌳测试理论 / 用例设计
①
从公司文档学习:白盒测试,黑盒测试,测试流程,测试用例的设计过程
②
讲讲你们自动化测试这一块的内容
③
实习时,git 如何协作
④
公司的工具链是怎样的
🍌工作内容
①
主要工作:API 二次开发自动化测试
②
负责公司 API 的自动化测试,优化覆盖率,确保 API 功能与平台一致
③
使用 C++ 和 Catch2 框架,进行单元测试和集成测试
④
使用 Git 进行版本控制,参与 GitLab 分支管理,合并分支冲突
⑤
通过录宏 / 跑宏,交互式 / 非交互式相结合的方式测试
⑥
在 GitLab 登记问题,提供详细复现步骤和日志文件,便于开发快速定位问题
四,交互式 / 非交互式自动化测试实践
📱交互式自动化测试
①
利用公司平台已有的宏测试系统实现自动化测试
②
优点:测试上下文易构建,通过平台操作,测试场景容易扩展,直接在平台添加测试宏
缺点:交互式只能构建正常场景;测试接口容易受其他模块干扰;不贴合客户真实使用场景
③
适用场景:平台命令封装类接口;UI 界面相关的显示接口
👖非交互式自动化测试
①
使用 Catch2 单元测试框架实现
②
优点:贴合客户使用场景;更容易实现参数层面的枚举
缺点:
测试上下文难构建,只能调用其他 API 进行
部分入参难以确定
部分命令场景,对入参有要求时,难以使用现有 API 指定
③
适用场景:数据查询类接口,数学计算类接口
📕手动确定覆盖率
①
当 OpenCppCoverage 的覆盖率还未更新,需要手动获取覆盖率时
②
可以在测试用例中,每个调用 API 的位置,打断点,f11,step into 源码
③
找到 API 的实现后,在每个小分支的入口处,都打上断点,然后针对测试用例中调用的 API,不断 step into,在源码中 continue
④
每跑进源码的一个小分支,就取消分支入口处断点
⑤
最后源码中尚未取消的断点,就是源码未覆盖的分支,然后针对未覆盖分支处调用的其他 API 和 参数,找开发问一下
五,测试工程构建 / 主要业务
🐎测试工程构建
①
类似启动 vs 工程时,双击 .sln 的操作:双击 generate.bat 构建出对应的 msbuild 工程并启动 VS2019
②
一个测试用例的构建包括:
a. 测试场景的初始化(测试上下文的构建
b. 执行被测试的 API 接口c. 对测试结果断言检查
③
对于 Catch2 测试框架,平台的运行环境属于全局变量,声明周期存在于整个测试流程中,所以每个测试场景运行结束时,无法自动还原环境,需要手动还原
🐘主要业务(API 的二次开发)
概念
①
二次开发,就是在现有软件的基础上,进行定制修改和功能扩展,实现自己想要的功能
②
客户需要借助二次开发的 API 进行开发
③
这个 API 接口可以理解为,根据客户的要求,将平台内部的某个函数,再做一层封装和处理后,再给用户使用
④
二次开发的 API 接口,一般对应平台的一个功能或子功能,而对于客户来说,他只需要知道 API 的函数名,功能,以及输入输出
客户如何工作
①
客户在开发环境中,引用二次开发 API 的头文件(声明),链接静态库 .lib(实现),就可以直接使用这批接口
②
客户通过这批 API,实现自己的功能,并且把代码封装成一个函数,将函数注册成自定义命令,然后编译生成动态库(.dll),也就是二次开发插件
③
接着运行我们的平台,加载 .dll 插件,就能使用用户自定义的功能了
API 和 平台的关系
①
API 测试一般对应界面上某个功能,与平台交互就能测试;
或者通过调用平台内部的功能函数进行测试
所以对 API 进行测试,也能测到平台的功能
②
API 测试用例失败,可能是二次开发 API 的问题,也可能是对应功能的内部函数出了问题
③
平台命令 / 底层函数的变更,也会对 API 产生影响
a. 显示影响:api 对应某个命令,命令发生了变化,比如增加了入参选项,对应的 api 也要同步更新
b. 隐式影响: api 使用了某个内部函数 或 底层机制,额你不函数逻辑变更,或机制变更
④
在不确定项目对 api 是否有影响时
a. 开发要主动向 api 开发进行确认
b. 测试也要提升 api 的覆盖率,并且及时全量地运行 api 自动化测试,检测项目是否引入 api 问题(master 主干分支,每周进行集成测试)
六,测试分级 / bug 排查 / 单元测试规范
🐱测试用例分级
① 功能性:有效等价类(输入性 + 功能性),边界,对平台运行环境的影响
a. 有些接口会影响全局变量,导致当前接口运行正常,但是其他测试用例因此失败
② 健壮性:无效等价类 + 其他常见错误异常场景
a.
接口会针对不同的错误场景,返回不同的错误码
一类错误码:客户经常犯的(非法输入),便于客户调试问题
另一类错误码:针对接口内部或平台底层代码的处理,出现异常(内存错误,读取对象数据失败),便于开发排查问题
b. 还要根据接口本身的功能,测试的经验,反推可能存在的错误场景
③ 易用性:命令规范,接口易用,注释正确
④ 兼容性
⑤ 效率测试
⑥ 回归测试
🐉BUG排查 / 定位
①
逐步排查接口 bug 时,不要用 for,会增大排查难度
②
复现问题,定位问题(用例 / 环境 / 接口实现 / 平台功能),查阅文档和源码,覆盖率分析和断点测试,和开发产品测试沟通,猜想与验证,查看日志,内存检测
③
特别注意:接口的依赖关系 + 前置条件 + 易混淆类型
🐒测试模式
① 传统测试模式
使用 TEST_CASE 定义单元测试
(test_name [,tags]) {}
test_name:测试用例名称,任意的,需要全局唯一
tags:标签名,用 [] 将每个标签抱起来,逗号分隔,不用全局唯一
② BDD模式
行为驱动开发(behaviour driven development),通过在 GIVEN,WHEN,THEN 中,自然语言书写测试用例的信息,扩展了测试驱动开发的方法
Ⅰ 其中,GIVEN 前提条件 = 测试场景初始化 + 构建入参
Ⅱ 然后,WHEN 做的事情 = 执行被测试 api
Ⅲ 最后,THEN 结果 = 对 api 调用后的返回值,入参,出餐进行检测
🏢单元测试规范
①
平台的测试案例使用 BDD-style,一个完整的单元测试案例包括
Arrange(初始化),Action(执行),Assert(断言)
②
编码原则:边界,正确输入,设计与需求相结合,错误输入,预期结果
③
编码规范:每个文件只对应一个接口 + 所有单元测试案例必须有描述
④ 测试维度
接口功能性(接口的正确性 + 一定条件下正常被调用 + 返回预期结果)
局部数据结构(变量是否有初始值 + 变量是否溢出)、
边界(指针 + 数值 + 字符串 + 集合)
异常处理(异常可否被识别 + 识别后是否有处理)
⑤
使用 OpenCppCoverage 工具,分析测试用例对代码的覆盖率
🍑优秀的单元测试
a. 自动化的
b. 很容易实现
c. 第二天还有意义
d. 任何人都可以一键运行
e. 运行速度很快
f. 结果稳定,多次运行同一个测试用例,总是返回相同的结果
g. 能够完全控制被测试的单元
h. 完全隔离的(不依赖其他所有测试用例)
i. 如果它失败了,可以很容易发现什么是预期的结果,可以很快定位问题所在
🌼单元测试细节 / 项目单测流程
单元测试细节
a. 底层遗留代码的测试:静态测试(开发自己代码走查)+ 动态测试(编写测试用例)
b. 是否为所有共有接口编写用例:资源有限时,有限覆盖高频,关键,危险接口
c. 推动开发改造接口:剔除非必要接口,提高接口容错能力,重构复杂接口,配套demo测试
d. 测试方案:确定子模块测试顺序,获取接口清单 和 调用频次,重点关注危险行为
e. 测试过程:对照接口标注和注释,检查逻辑完整性,异常处理,全局变量使用,边界等
f. 测试用例编写:结合黑盒 / 白盒,逻辑覆盖,边界,错误场景补充;模拟平台实际调用,发挥想象力设计异常输入
项目单测流程
Ⅰ 输入:项目核心文档 + 设计方案
Ⅱ 输出:测试方案 + 测试报告 + 用例代码
Ⅲ 执行流程
a. 初步拟定测试方案,用例清单
b. 用例评审(上级,项目经理,项目开发)
c. 搭建测试环境,测试场景构建
d. 引入开发代码,运行测试用例
e. 反馈测试结果,bug 跟踪,生成测试报告
f. 开发人员处理 bug 后,进行回归测试
g. 测试代码集成(开发源码集成后) -- 集成:多个组件模块合并到同一个系统
h. 测试用例加入主干定期测试
七,代码质量问题案例
🌊案例
① 超大文件
单个源文件几千行,很难维护和理解,如果某个超大的 .cpp,被多个人同时修改,合并冲突的概率就会大大增加,影响开发效率
② 超长函数
函数超过 200 行,逻辑复杂,难以测试和复用,如果某个超长函数被修改,容易引发连锁 bug,导致回滚上线
void processOrder() {// ... 500行代码 ...}
③ 超长的类
类中成员变量和方法过多,违反单一职责原则,最好拆分为多个小的类,按功能聚合
④ 单个函数多个职责
一个函数既做数据校验,又做业务处理,还做日志记录
void handleUserRequest() {validateInput();processBusiness();logOperation(); } // 建议拆分为 validate(), process(), log() 三个函数
⑤ 需要频繁更新的函数
频繁变动的函数,容易引入回归 bug,如果某个函数的逻辑频繁变动,未做好单元测试,容易导致线上事故
⑥ 不必要的接口依赖
如果模块 A 依赖模块 B 内部的实现,导致 A 的变更影响 B,最好进行接口隔离,减少耦合
⑦ 不必要的实现依赖
如果项目直接依赖第三方库的实现细节,升级时风险很大,容易产生大量编译错误
⑧ 逆模块依赖的接口调用
如果低层模块反向依赖高层模块,会破坏分层结构
⑨ 模块私有接口被外部调用
本应只在模块内部使用的函数,被外部调用,导致维护困难
⑩ 滥用全局变量
全局变量被多处修改,难以追踪和调试,比如被多线程同时修改,导致数据错乱
① 不必要的局部静态变量
静态变量声明周期长,容易引发资源泄露或线程安全问题
② 难理解的名称缩写
比如 tmp, foo, bar
③ 抽象的函数命名
比如 doTask(), handle(),无法体现具体功能
④ 不必要的模板编程
追求泛型而滥用模板编程,导致代码复杂难懂,新人难以上手维护
⑤ 无意义的函数注释
注释应说明返回值,入参,出参,边界,注意事项等
// this is a function
⑥ 过长的参数列表
函数参数不要超过 5 个,否则易出错,考虑封装为结构体或对象传参
⑦ 缺乏单元测试
无单元测试,线上热修复容易引发故障
⑧ 重复代码
同样的逻辑在多个地方实现,维护成本高,最好提取为公共函数或工具类
⑨ 通用处理方法--插入大量针对个别对象的特殊处理逻辑
如,主流程代码被特殊 case 污染,导致难以阅读和修改
⑩ 对非 pod 对象进行裸内存操作
如,直接 memcpy, memset 非 POD 类型对象,容易产生 undefined 行为
⑩① 混合编码风格
C/C++ 混用,代码风格不统一,容易内存泄露
🌼POD 对象
什么是 POD
Plain Old Data,普通的旧的数据
POD 类型:像 C 语言结构体那样,没有复杂构造 / 析构函数,没有虚函数,没有继承,没有非静态 C++类 或 结构体
本质:内存布局简单,和 C 语言的 struct 一样,可以安全使用 memcpy, memset 等 C 函数直接操作内存的对象
裸内存操作:
直接对内存进行原始的字节级别的操作,而不是经过对象的构造,析构,拷贝等 C++ 语义
比如 memcpy(拷贝一段内存),memset(填充一段内存)
malloc / free(直接分配 / 释放原始内存块,不调用构造 / 析构函数)
realloc(直接调整内存块大小)
本质:
① 只关心内存的字节内容,不关心对象的声明周期,构造,析构,拷贝等 C++ 语义
② 适用于 C 语言风格的 POD (老古董类型)对象
③ 对于有复杂成员(std::string, vector, 虚函数, 继承等)的 C++ 对象,裸内存操作,会破坏对象内部的状态,导致 Undefined 行为
建议
① 判断 POD 场景
a. 只有简单结构体(全是 int, float, char 等基础类型的 struct)
b. 只要有 C++ 特性(string, vector, 虚函数, 继承, 构造, 析构等)就不是 POD
② 不要对非 POD 对象用 memcpy / memset
比如
struct User {std::string name;int age; };User u1, u2;memcpy(&u2, &u1, sizeof(User)); // wrong! User not POD!
③ 安全做法
a. 对于非 POD 对象,通过拷贝构造,赋值运算符,swap 等 C++ 标准方法,不用 memxxx()
b. 如果需要序列化 / 反序列化,用 protobuf, json 等库,不要直接 dump 内存
④ 工程建议
a. 写工具类 / 测试代码时,如果需要内存填充,清零,二进制比较,需要确认对象是 POD
b. 代码评审时,看到 memcpy / memset 操作对象,需要确认必须是 POD
c. 如果对含 std::string 的 struct 做 memcpy(),会导致线上服务频繁 core dump