做电影网站的成本,学网站维护,wordpress用那个采集器,怎么找客户渠道到底什么是单元测试 这个问题看似非常简单#xff0c;单元测试嘛#xff0c;不就是咱们开发自己写些测试类#xff0c;来测试自己写的代码逻辑对不对。 这句话没有问题#xff0c;但是不够准确。 首先我们要明白#xff0c;这个测试二字前面还有两个字#xff1a; 单元 。… 到底什么是单元测试 这个问题看似非常简单单元测试嘛不就是咱们开发自己写些测试类来测试自己写的代码逻辑对不对。 这句话没有问题但是不够准确。 首先我们要明白这个测试二字前面还有两个字 单元 。 它要求我们的测试粒度小 具体来说就是一个 Test 仅测试一个方法对这句话的认识非常重要。 市面上常见的错误单测是怎样的呢 把整个项目启动开始玩真的调用入参是数据库里面真的值所有的操作都落库一个 Test 从 controller 到 service 再到 dao一条龙打通。 这种不叫单元测试 这叫集成测试 。 如果你现在写的是这样的“单测”你就会发现写个测试类不仅要依赖数据库还要依赖缓存依赖公司别的团队的服务亦或是一些三方开放平台的 Http 服务。 当我们的测试类需要依赖太多太多外部因素的时候只要有一个地方出现问题你的测试就是 fail 的。 并且入参和出参不能“任你摆布”你还得想着如何控制别的团队的服务返回你想要的数据。 比如我想测试当依赖的服务 A 返回 sucess 时我的代码逻辑的正确性还得想测试服务 A 返回 fail 的逻辑还想测试它返回 null 的逻辑。 再包括数据库或者缓存的一些返回值的定制这非常的困难已经开始劝退人了。 然后 把整个项目启动 这通常需要花费数分钟甚至数十分钟的时间写两个单测一下午过去了时间都花在调试的启动上了。 所以才会有那么多程序员觉得单测好难写啊又耗时还动不动就 fail写个 P。 所以回过头来看到底什么是单测 在 Java 中单元测试的对象是类中的某个方法一个 Test 只需要关心这个方法的逻辑正确性仅仅测试这个方法的逻辑不应该也不需要关注外部的逻辑。 举个例子当你写 service 的单测时候你压根就不应该测试 dao 或者外部服务返回的对不对这是属于它们的逻辑跟我 service 没有关系。 可能听着感觉不强烈我拿代码举个例 假设我们要测试 trainingYes 这个方法可以看到方法内部依赖 yesDao 和 OneOneZeroProvicer 一个是数据库一个是 RPC 服务。 这时候我们的思维应该是不管传入的 id 在数据库中对应的 yes 数据到底如何我想让 yesDao 返回 null 的时候它就要返回 null 想让它不为 null 就不为 null。 对 OneOneZeroProvicer 也是一样我想随意操控让它返回 false 或者 true。 因为数据库和外部服务的逻辑跟我当前的这个 service 方法没关系 我只需要拿到我应该拿到的值来测试我的方法内部的所有逻辑分支即可 。 只有这样我们才能容易的测试到我们所写的代码逻辑。 你想想看如果你要是测着 trainingYes 还得管着到底哪个 id 能拿到值啊然后这个 yesDao#getYesById 内部逻辑有没有状态过滤啊这个 id 对应的数据有被废弃吗需要关心这个那个这就非常累了。 再或者你想关心 OneOneZeroProvicer#call 怎样才能返回 true怎样才能返回 false这就更难了因为这是别的团队的服务你连这个服务的代码权限都没一个一个去问别人 万一没这样的数据呢还得去造 总而言之单元测试仅需要关注自己方法内部的逻辑不需要关注依赖方。 看到这很多同学就搞不懂了那该怎么搞我的代码就是依赖它们的服务了啊。 这就涉及到 mock 了。 mock 指的是伪造一个假的依赖服务替换真正的服务在上面的例子中需要伪造 yesDao 和 OneOneZeroProvicer 我们操控它得到我们想要的返回值满足我们自身对 trainingYes 的测试需求。 我拿 yesDao 举例一下如下所示我 mock 了一个假的 dao 然后 在单测时通过反射或者 set 注入的方式把 MockYesDao 注入到测试的 YesService 中 这样一来是不是就能控制逻辑了 当我传入的 id 是 1 的时候百分百拿到一个不是 null 的 yes 对象当传入其他值的时候肯定拿到的是 null这样就非常容易控制我要测试的逻辑。 当然上面仅仅只是举例说明 mock 的含义的具体作用方式实际上真正单测的时候没有人会手动写 mock 服务基本上用的都是 mock 框架。 比如我用的就是 mockito这个我们后面再提。 至此你应该对如何写单测有点感觉了我简单总结下上面说的几个小点 1. 单测不应该启动整个项目包括 Spring 容器没有这个必要耗时长。 2. 单测不应该关心依赖的服务包括 Dao、provider等其它服务需要通过 mock 来解耦。 3. 一个测试方法只测当前要测试的一个类中的一个方法。 其实就是分而治之的思想本身在写代码的时候你已经为了降低复杂度和解耦把代码分成了一个一个模块一个个方法而单元测试的目的本就是验证这些你拆分的方法自身逻辑的正确性。 为什么单测这么难写 在对单测有点感觉之后我们再来盘一盘为什么单测这么难写。 核心原因在于 我们本身写的代码不够解耦 。 看到这有人不服了什么单测难写还怪我本身写的代码不好难写是因为本身的业务逻辑复杂 好吧这里需要强调一下逻辑简单的类其实没必要写单测一般只是领导要求纯粹的追求覆盖率的时候才会把这种简单的类补上去。 举个很简单的例子 studentService.getStudentById(Long id) 我相信你都能脑补里面的逻辑你要说你就想为这样的方法写单测这当然可以但是收益不大。 单测收益最高的就是针对那些复杂的场景比方说在开发周期比较紧急的时候核心的、容易出错的逻辑才是更应该去重视的地方。要是开发周期空闲你要补哪都行 回到单测难写的问题上用专业术语来讲就是 你写的代码可测试性不高 导致难以编写对应的单测类。 怎样的代码是可测试性不高呢我举个非常简单的例子 假设你要给 garbageMethod 写个单测是不是有点难 里面用到了静态方法又 new 了个service。 这静态方法我想让返回值等于 111我只能去研究里面的逻辑。有人可能想不就是一个方法的逻辑吗就看看呗。 那就看看 可能你会说这两分钟我就看明白了但是这才一个要是好多都需要看呢 你为了测试当前的方法且花了一堆时间去理解别的不需要测试的类的逻辑这做法本身就不符合逻辑。 然后那个 noSevice 是 new 的这如何控制它的返回值啊我想 mock 这个类也替换不了啊 所以这样的代码就是可测试性低的代码不好 mock 当然mock 框架支持静态方法的 mock不过new noSevice 不好弄当然一般人都有不会这样写的我只是为了举例 还有各种类之间有继承关系的这种测试难度都比较大。 就是上面的种种原因导致我们的单测难以编写。 所以如果我们在设计接口的时候先编写单测我们写出来的代码其实可测试性就很高了因为你完全晓得这样的写法会使得你单测很难进行下去自然而然你写的代码就会往解耦的方向发展比如上面的 noService 肯定会注入。 我来列举下具体哪几种代码写法使得我们单测难以编写 1. 静态方法不好mock替换注入不过现在mock框架已支持 2. 内部直接 new 强依赖无法 mock 替换注入 3. 继承类测试当前类的方法逻辑还需要关心父类逻辑和mock父类的服务所以我们常说组合优于继承 4. 全局变量这个应该好理解好方法都公用你改了值之后会影响别的测试类特别是并发执行测试类时就傻了 5. 时间等一些未决行为代码里面有 new Date逻辑是近 15 天可行然后超过 15 天就跑不通了当然可以通过动态计算时间 这里我要强调下我不是说上面的这几种代码不能写这是不现实的我只是列举说明这几种可能会使得你的单测不好写 当然第 2 点就是不能写的 。 写个单测例子 说了那么多不如实战一下我就拿 trainingYes 来举例说明这里引入 mockito 测试框架。 可以看到通过注解 mock 了需要 mock 的 dao 和 provider 然后将其注入到我们要测试的 yesService 中。 接下来就是具体的逻辑根据场景我一共写了 4 个方法来测试 里面的 when(xxxx).thenReturn(xxx) 就是我们指定的 mock 逻辑这就是指哪打哪随心所欲。 我们跑一下你看就很快59 ms也不需要 Spring 框架。 就是通过这样的 mock 手段忽略了依赖的服务的逻辑使得我们要它怎样就怎样便于我们单测类的编写。 至于具体的 mockito 的使用方式这篇就不做展开了网上看看应该简单的。 然后上面提到的静态方法的模拟也简单的我截个网上的例子 上面的逻辑就是模拟静态方法 StaticUtils.name 跟普通对象不同的是它用完之后需要 close 一下所以用了 try-with-resource当然也可以手动 close原理也不做展开有兴趣的小伙伴可以自己去了解下。 看到这想必你对单测应该已经挺有感觉了吧? 道阻且长 知道了单测如何写和为什么难写之后其实我们的思路已经清晰了但是往往现实还是残酷的。 以前的老代码巨多领导要求补难 一个 service 依赖十几个服务mock 都 mock 傻了难 项目太紧急了从长远来看单测的收益会使得整体开发和后期维护的时间短但是领导就是要求下周一上线难 我个人认为一些稳定的代码除非现在真的没事做了完全没必要去补单测完全可以在改动对应的点的时候再去补然后新写的方法都要求上单测这是非常合理的。 如果写业务的时候同步写单测会促进你的思考缕清思路写出的代码因为可测试性高自然而然就比较漂亮和解耦。 还有一点也很重要其实我们写单测的时候不应该过多的关注内部的逻辑举个非常简单的加法例子我们单测只关心 add(1,1) 的结果是 2我管你里面是的实现到底是位运算还是啥运算 因为只有当我们的单测没有过度的关心内部实现时之后方法的具体实现变更从普通的 变成了位运算我们的单测才不需要进行对应的修改。 但实际上这种情况对我们业务不太适用。 举个例子 YesService 之前依赖 yesDao 现在这个 Dao 被剥离了变成了另一个 RPC 服务对应的我们之前所有的测试用例还是需要更改的这是没办法的事情。 不过为什么我还要提一下这点呢 比如你的测试方法里面有个 xxxService.save 逻辑这个方法没有返回值后面的逻辑也不依赖它那么就不要想着在单测是时候写 verify(xxxService.save(..)); 来验证这个方法是否被调用。 这样验证是否被调用其实意义不是很大并且之后如果 xxxService 被移除了单测就抛错了因为里面没有调用 xxxService.save 你还需要把这个单测给修复了。 这就是我所说的写单测的时候不要过度关注方法内部实现有些需要mock的没办法。
感谢每一个认真阅读我文章的人礼尚往来总是要有的虽然不是什么很值钱的东西如果你用得到的话可以直接拿走 这些资料对于【软件测试】的朋友来说应该是最全面最完整的备战仓库这个仓库也陪伴上万个测试工程师们走过最艰难的路程希望也能帮助到你!有需要的小伙伴可以点击下方小卡片领取