回归、集成与单元测试
在软件开发进程中,测试环节至关重要,主要涵盖回归测试、集成测试以及单元测试。
回归测试旨在检验软件在功能修改或增添后,原有功能是否依旧正常运作。其核心在于保障软件于变动后仍可精准运行,杜绝新错误产生或旧功能受损。以抖音开发为例,新增功能后,质保部门会在终端模拟刷抖音、看评论等场景,以此验证功能正确性。
集成测试聚焦于多个模块或组件协同运作的精准性,重点排查模块间交互与接口的正常性,确保软件系统集成后稳定运行。软件开发常将系统拆分为多个模块,各模块各司其职,而集成测试便是将这些模块整合并全面检测,以确其协作无间。
单元测试处于开发阶段,由开发者对单个函数模块开展功能测试,用以核实程序中独立模块(多为函数或方法)能否按预期运行,此乃软件开发中最小测试单元,旨在确保各单元功能达标。
从层级上看,自上而下测试覆盖率递增,成本递减。故而单元测试覆盖率在一定程度上可反映代码质量。单元测试成本低、覆盖率高,能保障单元功能无误;集成测试成本适中、覆盖率稍低,专注于模块协作验证;回归测试成本偏高,用于验证系统整体的稳定与功能。实际开发中,常结合这三种测试方式,以全方位保障软件质量与稳定性。
单元测试流程包含输入、测试单元、输出与校对环节。“单元”范畴广泛,涉及接口、函数、模块等。借助校对环节确保代码功能契合预期,通过对比输出与期望值判定代码正确性。单元测试兼具质量保障与效率提升功效。新代码编写并加入单元测试后,在代码整体覆盖率达标的情形下,既能确保新功能准确无误,又能维护原有功能稳定;代码出现漏洞时,编写单元测试有助于快速定位并修复问题。
单元测试有如下规则:其一,测试文件均以 _test.go 结尾,以便区分源文件与测试文件;其二,单元测试函数命名需以 Test 起始,且其后首字母大写,如 TestDemo;其三,单元测试设有 TestMain 函数,此为 Go 语言测试包(testing)的特殊函数,在测试前后执行初始化与收尾操作,是整个测试包的入口函数,可取代默认入口点 func TestXxx(t *testing.T)。其代码结构为在所有测试函数执行前运行,可进行全局初始化与清理操作,随后调用 m.Run() 启动所有测试函数执行,且因是入口函数,仅执行一次。需留意测试函数并行执行,不可依赖其执行顺序。
以下为单元测试实例:先于 demo2.go 文件创建 HelloTom 函数,预期返回 "Tom"(此处为测试故意设为返回 "Jerry"),再生成测试文件 demo2_test.go(使用 GoLand 可通过快捷键 alt + insert 并选择 Test file 生成)。在 demo2_test.go 中编写测试代码,运行 HelloTom 函数获取返回值 output,设定预期输出 expectOutput 为 "Tom",然后通过 if 语句或 assert 包中的 assert.Equal 函数校对 output 与 expectOutput 是否一致,若一致则测试通过,反之则失败。
单元测试的主要评估指标是代码覆盖率,覆盖率越高,表明经测试的代码越多。例如在 demo3.go 文件中编写判断分数是否及格的 judgePass 函数,生成测试文件 demo3_test.go 并调用 judgePass 函数传入特定分数进行校对。运行带覆盖率的测试(可使用命令 go test demo3_test.go demo3.go --cover 或在 GoLand 中点击右上角 run with coverage),测试工具会自动执行测试用例并统计代码执行行数,进而得出覆盖率结果。若仅测试单一情况,可能存在部分代码未被执行,导致覆盖率未达 100%,此时可通过增加测试用例提升覆盖率。
实际项目里,测试依赖的组件往往繁杂,如数据库、文件、cache 等,这些属于强依赖,即模块间相互依存度高,某一组件变动会影响依赖它的其他组件。单元测试追求幂等与稳定,幂等即重复测试结果相同,稳定即单元测试相互隔离,随时可运行。但直接编写单元测试可能因存在网络等依赖而不稳定,可采用 mock 测试予以解决。
最后,以文件依赖为例阐释单元测试中的依赖问题,先创建 log.txt 文件,后续可基于此展开相关测试与依赖分析。