首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >重构不崩、bug少80%:单元测试实战指南

重构不崩、bug少80%:单元测试实战指南

原创
作者头像
不做虫子
发布2025-09-24 16:35:46
发布2025-09-24 16:35:46
2100
举报

单元测试的定义

单元测试是指对软件的单个单元或组件进行测试,目的是验证每个代码单元是否按预期执行。

比如一个函数、一个方法或一个接口等。

为什么要写单元测试

单元测试的意义

尽早发现问题

经常写 bug 的同学都知道,发现 bug 容易,解决 bug 难。

因为解决一个 bug 时,可能会同时引入其他 bug,导致局面失控。

单元测试不仅能保证当前代码的正确性,还能确保不会影响已有的代码。

保证重构的正确性

我们为什么总是在不断写 if else?

其实是因为害怕。

我们到底在害怕什么?

我们害怕一动代码,整个“屎山”就崩了。

如果有足够覆盖率的单元测试,就可以放心重构,不用担心系统崩塌,重构时也没有心理负担。

优化代码设计

有人觉得单元测试不好写,改动频繁,没什么用。

其实多数情况下,是因为代码设计不够好。

有时候操作很猛,但一看圈复杂度高达 25,函数设计不合理,只关注功能实现而忽略了可测试性,这样就很难编写单元测试。

在编写单元测试时,我们会跳出当前功能函数,从输入和输出的角度思考:这个函数是否实现了高内聚低耦合?它依赖的其他函数是否合理?功能是否足够单一?仅通过输出能否判断函数是否正常工作?

这种方式会促使我们反思代码设计,同时加深对整个系统的理解。

单元测试就是文档

有人说不需要写注释,因为某位大师说过“代码本身就是注释”。

但现实是,一个函数几百行却没有任何注释,即使大师来了也看不懂。

其实,比没有注释更糟糕的是过时的注释

如果一个单元职责足够单一、可测试性好,那么它的测试用例就是最好的说明书,而且不会过时。因为一旦过时,测试用例就无法通过,系统会强制你更新它。

如何设计测试用例

基本功能

把需求转化为测试用例,什么样的需求或功能就对应什么样的测试用例。

比如:计算房间的推荐分数并进行排序。

输入:房间分数涉及的各项指标。

输出:按指定顺序排列的房间列表。

边界值

很多时候,bug都是由边界值引起的。

比如边界条件、并发等问题。

设计测试用例时,要把边界条件也考虑进去,验证输出是否符合预期。

分支路径

程序由数据和控制流程组成。

测试时尽量覆盖每一个逻辑分支,避免遗漏。

如何写单元测试——以 gomonkey 为例

给一个函数打桩

代码语言:javascript
复制
patchConf := gomonkey.ApplyFunc(config.GetBusinessConfig, func() *config.BusinessConfig {
    return &config.BusinessConfig{
        GameNotifyConf: config.GameNotifyConfig{
            DayLimit: 20,
        },
    }
})
defer patchConf.Reset()

给一个共有方法打桩

代码语言:javascript
复制
func TestApplyMethodFunc(t *testing.T) {
    slice := fake.NewSlice()
    var s *fake.Slice
    Convey("TestApplyMethodFunc", t, func() {
        Convey("for succ", func() {
            err := slice.Add(1)
            So(err, ShouldEqual, nil)
            patches := ApplyMethodFunc(s, "Add", func(_ int) error {
                return nil
            })
            defer patches.Reset()
            err = slice.Add(1)
            So(err, ShouldEqual, nil)
            err = slice.

Remove(1)
            So(err, ShouldEqual, nil)
            So(len(slice), ShouldEqual, 0)
        })
    })
}

给私有方法打桩

代码语言:javascript
复制
func TestApplyPrivateMethod(t *testing.T) {
    Convey("TestApplyPrivateMethod", t, func() {
        Convey("可以给不同包里的私有值方法打桩", func() {
            s := fake.PrivateMethodStruct{}
            patches := ApplyPrivateMethod(s, "haveEaten", func(_ fake.PrivateMethodStruct) bool {
                return false
            })
            defer patches.Reset()
            result := s.

AreYouHungry()
            So(result, ShouldEqual, "I am hungry")
        })
    })

}

总结

单元测试像一面镜子倒逼代码设计走向简洁——毕竟难测试的逻辑,往往藏着耦合的“暗礁”。

当单元测试的“地基”扎实了,高覆盖率、CI持续绿码会成为常态,系统稳定性与用户满意度也会沿着测试链路自然生长。与其在“屎山”里焦虑“不敢动”,不如现在就为代码穿上“测试铠甲”——毕竟,写测试的1小时,远胜过线上debug的3个通宵(真的)

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 单元测试的定义
  • 为什么要写单元测试
    • 单元测试的意义
      • 尽早发现问题
      • 保证重构的正确性
      • 优化代码设计
      • 单元测试就是文档
  • 如何设计测试用例
    • 基本功能
    • 边界值
    • 分支路径
  • 如何写单元测试——以 gomonkey 为例
    • 给一个函数打桩
    • 给一个共有方法打桩
    • 给私有方法打桩
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档