第1篇: 讲述了如何创造"缝".  "缝"(seam)是需要知道的概念.

第2篇, 避免在构建对象时写出不易测试的代码.

第3篇, 依赖项和迪米特法则.

本文是第4篇, 将介绍全局状态引起的问题.

全局状态

全局状态, 也可以叫做应用程序状态, 它是一组变量, 这些变量维护着应用程序的高级状态.

在程序里, 全局状态可能都存放在一个全局状态对象里, 例如ASP.NET里面的HttpContext; 或者它们可能是全局的变量, 这些全局变量在程序的任何地方都可以访问.

不管是如何实现的全局状态, 每个全局状态变量在内存里只有一个实例. 所以如果一个类里更新了全局变量的值, 那么另一个类访问该变量的时候它的值就是刚才被更新的值.

有些情况下, 使用全局状态确实有用; 但是如果使用不当, 则会对测试造成很大的影响.

全局状态对测试引起的问题

  • 使用静态方法或全局变量访问全局状态的时候, 就引起了对全局状态的直接耦合. 这很不好.
  • 这种耦合就导致很难对测试进行设置. 针对每个测试, 我们必须创建和设置好存储全局状态的对象. 或者把全局变量设定为所需的值.
  • 因为每个全局状态变量在内存里只有一个实例, 那么我们就无法进行并行单元测试了. 如果我们为A测试设定了全局变量的值, 然后在测试A结束前开始测试B, 这时测试B修改了全局变量的值, 这时测试A就可能会失败, 因为它所期待的全局变量不是这个值.
  • 上面的这种现象就叫做鬼魅般的超距作用(Spooky Action at a Distance). 而实际项目中确实经常发生这样的情况, 并行跑单元测试的时候偶尔会失败, 而单独去跑失败的测试时却一直成功. 这种耦合到全局状态的测试就不能再称为隔离测试了.

危险信号

  • 全局变量
  • 调用静态字段或调用拥有静态字段的类的静态方法. 但也仅限于该类的静态方法使用了该类的静态字段.
  • 单例模式 (Singleton Pattern)
  • 单元测试会随机的失败, 但是又没发现明确的原因.

解决办法

  • 尽量使用本地(局部, 越窄越好)状态变量
  • 如果第三方库使用了静态方法, 那么应该使用一个包装类来对该方法进行包装. 这个包装类还是要实现一个接口. 用它的时候注入该接口即可. 这样测试的时候就可以为包装类创建测试替身了, 并把全局状态解耦.
  • 使用可依赖注入(IoC/DI)的单例体, 这种单例体是由IoC容器创建的.

例子

就举一个例子吧.

有这样一个获取当前登录用户权限的类, 它使用的是单例模式:

这个是典型的单例模式, 它会保证在程序中只返回一个实例, 这里就不多介绍了.

下面这个Service会调用上面这个Auth类:

Auth是单例模式的, 而且还调用了静态方法.

现在的状态是, OfficeService和Auth所包含的全局状态紧密的耦合到了一起.

如何解决问题

首先应该把单例模式去掉, Auth类只保留两个属性和一个方法:

然后在service里面应该注入IAuth接口并使用:

那么接下来就需要保证这个IAuth无论在程序中注入了多少次, 都是同一个实例.

这时就需要使用依赖注入(DI) 库了. 现在的DI库通常允许指定IoC容器中每对绑定服务的作用范围(Scope), 或叫做生命周期管理.

例如ASP.NET Core内置的IoC容器就内置了这种功能. 在ASP.NET Core 项目的Startup类里, 这样写就可以保证每次请求IAuth的时候只会得到同一个对象实例:

现在这个"单例"的工作是由IoC容器来负责了. 在其它地方正常的注入IAuth使用即可.

先写到这, 本文的概念性内容和更多的例子请参考Angular创始的人这篇文章: http://misko.hevery.com/code-reviewers-guide/flaw-brittle-global-state-singletons/

.NET Core TDD 前传: 编写易于测试的代码 -- 全局状态相关推荐

  1. .NET Core TDD前传: 编写易于测试的代码 -- 缝

    有时候不是我们不想做单元测试, 而是这代码写的实在是没法测试.... 举个例子, 如果一辆汽车在产出后没完成测试, 那么没人敢去驾驶它. 代码也是一样的, 如果项目未能进行该做的测试, 那么客户就不敢 ...

  2. .NET Core TDD 前传: 编写易于测试的代码 -- 依赖项

    第1篇: 讲述了如何创造"缝".  "缝"(seam)是需要知道的概念. 第2篇, 避免在构建对象时写出不易测试的代码. 本文是第3篇, 讲述依赖项和迪米特法则 ...

  3. .NET Core TDD 前传: 编写易于测试的代码 -- 构建对象

    该系列第1篇: 讲述了如何创造"缝".  "缝"(seam)是需要知道的概念. 本文是第2篇, 介绍的是如何避免在构建对象时写出不易测试的代码. 本文的概念性内 ...

  4. 单元测试:如何编写可测试的代码及其重要性

    原文来自互联网,由长沙DotNET技术社区编译.如译文侵犯您的署名权或版权,请联系小编,小编将在24小时内删除.限于译者的能力有限,个别语句翻译略显生硬,还请见谅. 作者:谢尔盖·科洛迪(SERGEY ...

  5. 如何编写可测试的代码 哈利勒的方法论

    Understanding how to write testable code is one of the biggest frustrations I had when I finished sc ...

  6. ORAN专题系列-18:5G O-RAN FrontHaul前传接口互操作性测试规范IOT概述与总体架构

    前言: 前传接口(FrontHual)是传统的BBU与RU之间的接口,在O-RAN之前,前传接口虽然定义了物理连接的CPRI标准,但CPRI之上承载的M plane和S/C/U plane的IQ数据, ...

  7. 用java写穿越火线代码_编写可测试的 JavaSript 代码

    无论我们使用和Node配合在一起的测试框架,例如Mocha或者Jasmine,还是在像PhantomJS这样的无头浏览器中运行依赖于DOM的测试,和以前相比,我们有更好的方式来对JavaScript进 ...

  8. ORAN专题系列-19:5G O-RAN FrontHaul前传接口M Plane互操作性测试IOT规范

    前言: 在<ORAN专题系列-18:5G O-RAN FrontHaul前传接口互操作性测试规范IOT概述与总体架构>阐述了5G O-RAN FrontHaul前传接口互操作性测试规范IO ...

  9. 《编写可测试的JavaScript代码》——1.4 小结

    本节书摘来自异步社区<编写可测试的JavaScript代码>一书中的第1章,第1.4节,作者: [美]Mark Ethan Trostler 译者: 徐涛 更多章节内容可以访问云栖社区&q ...

最新文章

  1. tensorflow models 工程解析
  2. Android短信管家视频播放器代码备份
  3. PX4修改线程内存大小
  4. 「压缩」会是机器学习的下一个杀手级应用吗?
  5. 你知道吗?OAuth2客户端有两种,认证方式有七种。
  6. ABAP中P类型介绍
  7. 找出最具竞争力的子序列_力扣300——最长上升子序列
  8. insert into语句_入门MySQL——DML语句篇
  9. 面试官问你Java内存区域你用new创建对象来解释
  10. 全国大数据分析系统基于vue echarts
  11. Atitit 版本管理----分支管理Atit
  12. 2020年第六届 美亚杯电子取证 团体赛 wp
  13. 安装软件时“应用程序无法启动,因为应用程序的并行配置不正确......”
  14. 他因“上帝粒子”获诺奖,却火速搬到乡下:它毁了我的生活
  15. java guardedby_java并发编程之Guarded Suspention
  16. Ubuntu Linux虚拟机不识别U盘问题解决
  17. 公众号推送长图最佳尺寸_公众号10W 排版攻略,长图无缝拼接一步做到!
  18. bzoj 2075: [POI2004]KAG
  19. ZBar源码分析(二)
  20. 计算机等级考试照片用ps怎么调,Photoshop教程:用PS消除照片中的杂色条纹

热门文章

  1. 计算机地址码特点,电脑摇头灯的地址码的设定问题你必须要注意的
  2. php传二维数组,JS用POST怎么传送二维数组给PHP
  3. python中exp_python中的exp是什么
  4. mysql的timestamp类型_MySQL数据库中的timestamp类型与时区
  5. mysql 恢复root用户_mysql误删root用户恢复方案
  6. java与bartender_Java调取Bartender使用教程.md
  7. 【caffe-Windows】caffe+VS2013+Windows+GPU配置+cifar使用
  8. 【js】vue 2.5.1 源码学习(二) 策略合并
  9. kprobe原理解析
  10. javaScript事件(一)事件流