V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
XiLemon
V2EX  ›  Java

业务代码写单元测试的最佳姿势是什么?

  •  1
     
  •   XiLemon · 2021-05-16 22:57:22 +08:00 · 9883 次点击
    这是一个创建于 1288 天前的主题,其中的信息可能已经有所发展或是发生改变。

    最近看了极客时间的 [ 10x 程序员工作法] 和 [设计模式专栏] ,里面有一些关于单元测试的内容, 看完之后,感觉好像懂了,但是回头一看自己写出的代码,却不知道如何下手。

    想请教一下大家,日常工作中是如何写单元测试的,团队的规范是怎样的,怎么迈出单元测试的第一步!!!

    第 1 条附言  ·  2021-05-18 15:54:10 +08:00
    发帖之前在站内搜索过之前有关单测的帖子,最后回帖基本都是 3 种情况:
    1. 不写,没时间,业务变化太快,成本太高不值得
    2. 讨论什么是单元测试,比说单元测试不应该访问数据库等等
    3. 推荐写单元测试

    实际上我关注的是第 3 点,想了解一下有写单测的团队是怎样实践的,怎么写出可单测的代码,我想问的是方式和方法,结果大部分变成了劝退 0.0...
    59 条回复    2022-09-18 21:06:57 +08:00
    lhx2008
        1
    lhx2008  
       2021-05-16 23:00:35 +08:00
    面向对象设计,把代码分层好,这样底层就可以 mock 住,这样就可以开始跑单测了
    Jooooooooo
        2
    Jooooooooo  
       2021-05-16 23:00:53 +08:00
    和你说个秘密, 这些都是书上说的好听.
    XiLemon
        3
    XiLemon  
    OP
       2021-05-16 23:03:37 +08:00
    @lhx2008 现在的项目里代码分层比较乱,service dao 飞来飞去,有点难搞哇
    @Jooooooooo 不会吧,老哥,我看设计模式专栏的例子,还挺贴切的。就是转到公司的项目,感觉无从下手
    lhx2008
        4
    lhx2008  
       2021-05-16 23:07:59 +08:00
    @XiLemon #3 如果是 SQL CURL,可以考虑直接上集成测试吧,或者后端改成用临时数据库( h2, sqlite )来本地跑,不用 mock 了,如果涉及到到调出到外部的 API 再考虑 mock
    Jooooooooo
        5
    Jooooooooo  
       2021-05-16 23:19:00 +08:00
    @XiLemon 还有诸如 DDD, RESTful 之类的东西你会发现都是说的好听, 实践几乎没有的.
    fpure
        6
    fpure  
       2021-05-16 23:22:36 +08:00
    @Jooooooooo 确实,实践这一套的成本不低
    aragakiyuii
        7
    aragakiyuii  
       2021-05-16 23:25:01 +08:00 via iPhone
    我感觉得先写出来能写单测的代码...然而现实情况是由于时间问题能跑就行
    XiLemon
        8
    XiLemon  
    OP
       2021-05-16 23:40:50 +08:00
    @lhx2008 谢谢
    @Jooooooooo 0.0.. 没有实践过。。。
    @fpure 不能光看成本呀,收益如何呢
    @aragakiyuii 现实就是写的业务代码,对单测很不友好。假设时间够,我该怎么写出单测友好的代码呢,光看一些简单的例子,领悟不了 ─.─||
    ClericPy
        9
    ClericPy  
       2021-05-17 00:27:52 +08:00
    之前也强制自己尝试 TDD DDD 什么的, 后来发现自己抽象的不好是原罪... 不过单元测试一直写的比较完整, 所以至今没出过大问题, 而且每次上线前被测试拦住都有一种劫后余生的畅快
    mxalbert1996
        10
    mxalbert1996  
       2021-05-17 00:37:52 +08:00 via Android
    @lhx2008 事实上 mock 并不是一个很好的解决方案,如果可能的话应该尽量避免写出需要 mock 才能测试的代码。具体可以看这个:
    https://android.googlesource.com/platform/frameworks/support/+/androidx-main/docs/do_not_mock.md
    ccde8259
        11
    ccde8259  
       2021-05-17 00:49:21 +08:00 via iPhone   ❤️ 1
    什么 TDD DDD 最后都要败给 DDL……
    能上,能用,能赚钱才是王道。
    单测首先 Mock 环境就够喝一壶。Mockito 还是 PowerMock 得引入。H2 和 RedisMock 都要折腾。还有 RPC 需要 Mock……
    然后就是真正开始准备编码,单测里面你会发现自己代码根本没法单测……一个接口设计的不合理就要改接口来满足单测。具有上下文依赖的接口是不是要改显式注入?接口过于万能引起测试用例过多是不是需要拆分?代码覆盖率偏低是不是要调整?
    坑太多收益也很一般。多数应用生命周期很短,过功能测试就上。用完就扔导致多数单测都是赔本买卖。
    XiLemon
        12
    XiLemon  
    OP
       2021-05-17 01:10:26 +08:00 via iPhone
    @ClericPy 希望能分享一下经验
    @mxalbert1996 我理解也是,今天看了一下代码,一个方法,要 mock 四五个对象,完全没办法写单测嘛
    hallDrawnel
        13
    hallDrawnel  
       2021-05-17 01:28:51 +08:00
    单元测试要求在开始写代码的时候,就需要考虑可测试性,还得准备一堆基础设施才行,例如单元测试的时候日志系统和配置系统要能够更方便支持单元测试的场景,要比 mock 做得更方便才行,不能什么都靠 mock,至少基础设施要对单元测试友好。遗憾的是很多项目开始就没有考虑过,后期引入就代价很大。而且很多代码生命周期太短了,没啥测试的价值,引用个需求文档后期要改有个参考就行。
    BeautifulSoap
        14
    BeautifulSoap  
       2021-05-17 01:45:31 +08:00 via Android
    单元测试最头痛的还是数据库怎么办
    很多人都说 mock,但是我感觉实际上这么做对数据库来说并不是个好方法(坑一大堆)
    所以一般业务代码测试我都是直接用实际的数据库来做的(主要针对 repository 层,java 似乎叫 dao ?),在每个测试开始前都清空下测试用数据库就行了

    至于上层的测试,如 service 的测试里要不要 mock 掉 repository 接口之类的,我还不能得出明确的结论。可能 mock 掉比较好,不受下层实现的限制,但是有时候一些单元测试要 mock 的接口实在太多了,而且各种接口的依赖性非常复杂的话,光是 mock 都是个体力活,还不如直接用。
    ericgui
        15
    ericgui  
       2021-05-17 03:37:52 +08:00
    业务变动比较大,怎么测?今天写了 2 个 test case,明天业务变了,这俩 test cast 就要删了,这不是浪费时间么
    vemier
        16
    vemier  
       2021-05-17 08:50:02 +08:00
    尽量使用集成测试代替多层的单元测试就好了,复杂的功能可以另外加单测。这样后期如果重构的话,测试用例完全不用改动,也少了很多意义不大的 Mock 。
    bsg1992
        17
    bsg1992  
       2021-05-17 09:13:15 +08:00
    业务代码很难进行单元测试的。
    数据库没法进行测试,引入 InMemory DB 对数据库达不到测试的要求。
    如果要介入数据库还得写插入和清库的代码。
    第三方依赖也是一个问题。
    到最后你会发现,写业务代码一个小时,单元测试得写上一天
    witcherhope
        18
    witcherhope  
       2021-05-17 09:18:21 +08:00 via iPhone
    如果团队没实行没规范,独狼很难做下去,团队有人推动,单测保证高覆盖率收益还是很大的
    janxin
        19
    janxin  
       2021-05-17 09:18:23 +08:00 via iPhone
    什么 TDD 还是 BDD 之流,最终还是落地到 ADD
    leafre
        20
    leafre  
       2021-05-17 09:29:03 +08:00   ❤️ 5
    楼上这些单元测试是什么都没弄清楚
    pkoukk
        21
    pkoukk  
       2021-05-17 09:39:01 +08:00
    @ericgui 你这是集成测试才会出的问题。单测恰好就是规避这种问题的
    no1xsyzy
        22
    no1xsyzy  
       2021-05-17 09:42:12 +08:00
    1. 需要引数据库就不是单元测试了,已经算集成测试了。
    2. 其实单测是 Write Everything Twice 的思想。

    话说 you-get 这个项目,提需求是发个 PR,其中包括一个会 fail 的单测
    cxshun
        23
    cxshun  
       2021-05-17 09:47:47 +08:00
    @XiLemon mock 对象没啥问题,因为一些逻辑有可能是别人写的,你不关注,那就直接 mock 解决,返回你希望返回的数据就好了。至于对方返回有问题,那这是集成测试需要做的东西,单元测试仅关注你自己关注的那块就好了
    lightjiao
        24
    lightjiao  
       2021-05-17 09:55:59 +08:00
    1. 自动化测试确实好用,特别是业务场景越来越复杂的时候,能够指数级的降低测试成本
    2. 代码里把一个类所有依赖的第三方对象全都以构造参数的形式传递,方便 mock,比如
    ```
    class Download
    {
    private Http m_HttpClient;
    public Download(Http httpClient)
    {
    m_HttpClient = httpClient;
    }
    }
    ```
    3. 每个类自动化生成 mock 类,比如自动生成 HttpMock 、DownloadMock,这样在单元测试时可以方便的吧 mock 对象代替实际的对象传入做测试,这一步主要是单元测试框架完成的,测试时 mock 好每个依赖的 API 的输入输出即可

    这种做法可以做到 100%代码行数覆盖的单元测试,实际要完成覆盖多少,根据项目情况来吧
    11ssss
        25
    11ssss  
       2021-05-17 10:06:05 +08:00
    楼上不点名说了,这些确实是有成本,但是代码质量和接口健壮性甚至对开发者素质是有显著得提升的。但是你自己见识少没见过,不代表没人用没人落地,说话太绝对了。
    timi
        26
    timi  
       2021-05-17 10:07:32 +08:00
    我们推过一段时间单元测试,后来为了单元测试而测试,个人以为核心代码单元测试就得了,CRUD 用接口测试保障就可以了,弄一大坨屎一样的单元测试代码,不好看也不好维护
    ebingtel
        27
    ebingtel  
       2021-05-17 10:12:44 +08:00
    有了测试用例 项目维护起来 得心应手……时间成本随着迭代,越来越低
    www5070504
        28
    www5070504  
       2021-05-17 10:13:02 +08:00
    真的是方法论 我之前也特别相信这个东西,但是实际上是很难真正去实践的,

    如果要实践 TDD 测试代码要来回重构不说,需求文档那边就得写的详细, 但是现在这个行情,你看看有几家能把需求文档写明白的
    crazyhorse
        29
    crazyhorse  
       2021-05-17 10:14:39 +08:00
    做 feature test 比较好,刚开始很慢。坚持一段时间你会发现写代码效率高了不少,bug 更少,自己也不用去手动录入数据来做自测
    ChristopherWu
        30
    ChristopherWu  
       2021-05-17 10:19:34 +08:00
    https://www.v2ex.com/t/776279

    请看这个,PBT 测试才是正道
    GoLand
        31
    GoLand  
       2021-05-17 10:24:43 +08:00
    学习一下这个,面向 interface 编程,这样会发现单元测试很好写。
    https://github.com/bxcodec/go-clean-arch
    qwerthhusn
        32
    qwerthhusn  
       2021-05-17 10:30:58 +08:00
    之前公司突然要求单元测试代码覆盖率,于是我精通了 Java 的 PowerMock,Mockito 的使用,我甚至能把代码行和分支覆盖率提升到接近 100%,但是到了线上还是有 BUG 。、。。
    MonoBiao
        33
    MonoBiao  
       2021-05-17 10:35:16 +08:00
    @ccde8259 的确,太头疼了
    sdushn
        34
    sdushn  
       2021-05-17 10:35:54 +08:00
    看完[ 10x 程序员工作法]会获得[ 10x 薪资不] (手动狗头保命)
    CHANGEX929
        35
    CHANGEX929  
       2021-05-17 10:36:36 +08:00
    把涉及到其他服务的部分都 mock 掉,数据库、缓存、远程 api 之类的。
    RedisMasterNode
        36
    RedisMasterNode  
       2021-05-17 10:44:22 +08:00
    安利一下腾讯写的 Golang 的单元测试指引
    https://mp.weixin.qq.com/s/eAptnygPQcQ5Ex8-6l0byA
    laragh
        37
    laragh  
       2021-05-17 13:43:31 +08:00
    @sdushn 并不会 因为我们一个人干多个人的活也不会发多份工资
    XiLemon
        38
    XiLemon  
    OP
       2021-05-17 13:44:41 +08:00
    @no1xsyzy 我去了解一下
    @cxshun 我感觉可能是现在的方法写的太大了,一个方法做了很多事情,导致许村 mock 多个对象,导致单测很难写
    @www5070504 我理解需求文档没写明白的好,最后咋移交的,怎么开发的呢?
    @sdushn 不一定会,但能让自己变成一个靠谱的选手
    @RedisMasterNode 谢谢,我看一下
    fewok
        39
    fewok  
       2021-05-17 13:46:47 +08:00
    1 、你要有技术方案
    2 、你要有上下游入口和出口定义
    3 、基于入口和出口,搞定依赖、搞定场景数据准备、搞定入口调用
    4 、边写业务代码,边写集成测试,或者特变关键的 util,可以专门测试下
    5 、基于方法的单测,有一个是一个,我都认为是菜鸟
    qiumaoyuan
        40
    qiumaoyuan  
       2021-05-17 13:50:36 +08:00
    我的理念是不知道做了有什么用的事情就不去做。所有的问题在你对代码质量、开发效率精益求精的过程中自然会遇上,然后再找解决办法。方法是用来解决问题的,没有问题就无所谓方法。很多人研究方法都是在自己没有遇到问题的前提下进行,自然一头雾水。

    想想不写测试代码的时候,你是如何人工测试的。为什么有些地方你会进行人工测试,有些地方又很自信很放心的不测试?

    代码就是把重复性的事情一次教会给电脑,让电脑去做重复劳动,测试代码也一样。

    我觉得结合这两点,基本上就能理出写单元测试的动机和时机。先别管什么 TDD 不 TDD,饭一口一口吃。
    no1xsyzy
        41
    no1xsyzy  
       2021-05-17 13:53:49 +08:00
    @XiLemon 我发现我笔误了,是会 fail 的测试而不是单测。不一定是单测,而且通常来说它的需求是解析某网址,显然不应该是单测。

    不过我凭空推测,它估计不是因为 TDD,而是因为 Issues 被广告机爆了。
    kaedea
        42
    kaedea  
       2021-05-17 13:54:29 +08:00 via Android
    用来调试 API
    qiumaoyuan
        43
    qiumaoyuan  
       2021-05-17 14:58:39 +08:00
    补充一下 #40,其实还有很关键的一点:很多人业务代码没有能力写干净,写测试代码就一定会更加添乱,这种人写测试往往都会半途而废。
    wangyzj
        44
    wangyzj  
       2021-05-17 15:04:51 +08:00
    楼上老哥有几个写单元测试的?
    业务的变动和要求,你根本没时间写
    也就面试的时候会被问到
    理想和现实的差距
    gdtdpt
        45
    gdtdpt  
       2021-05-17 15:18:26 +08:00
    首先自身业务代码层次要清晰,各层级间耦合度要低,这样在 Mock 的时候工作量才会少,才能专注测试的内容,不然光写 Mock 就一堆代码。
    在一个 Service 方法里调用另外 3 、4 个 Service,然后又调用几次 Dao 不同的方法这种流水帐一样的代码写 Mock 都要写死人,怎么让人写单元测试。
    angmieee
        46
    angmieee  
       2021-05-17 16:04:22 +08:00
    业务代码不写单元测试。你是嫌加班不够多,还是工作不饱和?
    passerbytiny
        47
    passerbytiny  
       2021-05-17 16:12:50 +08:00 via Android   ❤️ 1
    单元测试有两个大前提:

    一、你不一定要 TDD (先写测试代码),但测试框架的设计一定要早于主代码框架的——单元测试是领导而不是擦屁股的。

    二、不一定是大项目,但一定是长期或者计划长期维护的项目——非自动化需要大量执行成本 /自动化需要大量设计成本,你不该为了半年甚至一天后就抛弃的项目做单元测试。
    ClericPy
        48
    ClericPy  
       2021-05-17 21:15:32 +08:00
    @ClericPy 我这边业务不是太复杂, 有时候单元测试有时候简单的功能测试, 太细致的单元测试就连 TDD 那本书作者都不建议 ( https://stackoverflow.com/questions/153234/how-deep-are-your-unit-tests , 也就是著名的那句 "I get paid for code that works, not for tests")

    有的同事天天加班到十一点, 进度天天催, 根本没时间写完整测试, 连文档都不愿意看, 最后布一套测试环境跑通核心功能验收就上线了. 毕竟对绝大多数公司来说, 开发人力根本严重不足, 方法论什么的行不通

    总结起来就是, 上线前一定要有测试, 但是不要迷信 100% coverage.
    searene
        49
    searene  
       2021-05-17 22:35:58 +08:00
    我一般只写集成测试,数据库也不 mock,直接去读,这种方法很少有人提倡,但是在实际的业务开发中是性价比最高的,既省时间又能保证程序的基本功能。传统测试理念要求一个功能写一个单元测试,甚至写功能之前就要写测试,比如 TDD,这样做得话听起来很不错,但是实际开发中根本跟不上需求的进度
    bthulu
        50
    bthulu  
       2021-05-18 08:36:24 +08:00
    @lightjiao 有时一个类会依赖十几个对象, 那这个构造函数就够大的了, 你还能区分构造函数里哪个参数是哪个吗
    witcherhope
        51
    witcherhope  
       2021-05-18 11:56:42 +08:00
    单测非常有必要,单测也是某种意义上的代码文档,在接手别人代码和重构时候就会知道单测意义所在了
    godall
        52
    godall  
       2021-05-18 12:58:41 +08:00
    @qwerthhusn 好奇问下,怎么方法能把测试覆盖率提高到将近 100%?
    ljf
        53
    ljf  
       2021-05-18 13:52:45 +08:00
    我司要求单侧覆盖超过 80%,代码才允许合并,所以慢慢变成为了写好单元测试而写代码,大部分时间都在写单元测试
    lix7
        54
    lix7  
       2021-05-18 19:39:04 +08:00   ❤️ 1
    小型工具方法写单测,业务逻辑写集成测试。
    业务逻辑写集成测试,mock 掉外部依赖(外部接口一类的),数据库不 mock,可以看下 testcontainers 项目,在测试部分启 MySQL / Redis 等容器。
    XiLemon
        55
    XiLemon  
    OP
       2021-05-18 22:17:27 +08:00 via iPhone
    @witcherhope
    @ljf 两位老哥、分享一波经验呀
    @lix7 多谢
    RandomJoke
        56
    RandomJoke  
       2021-05-19 16:45:02 +08:00
    从一些简单逻辑开始呗:
    1. 比如 API 的输入 check
    2. 比如相对简单但是共用的 util

    一般来说,你觉得 test 不知道怎么写,要么是代码实在写的有问题,要么就是本身就不该写,不过大部分不知道怎么写都是代码的问题,可能层次划分的不对,逻辑拆分的不够细等等
    MarioLuo
        57
    MarioLuo  
       2022-03-19 11:47:57 +08:00 via Android
    一年过去了,楼主单元测试有值得方向的经验吗
    XiLemon
        58
    XiLemon  
    OP
       2022-05-28 12:28:56 +08:00
    @MarioLuo #57 并没有,依然在裸奔
    chaosz
        59
    chaosz  
       2022-09-18 21:06:57 +08:00
    @lix7 赞同
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   4530 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 48ms · UTC 05:33 · PVG 13:33 · LAX 21:33 · JFK 00:33
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.