.NET Core TDD 前传: 编写易于测试的代码 -- 全局状态
第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 前传: 编写易于测试的代码 -- 全局状态的更多相关文章
- .NET Core TDD 前传: 编写易于测试的代码 -- 缝
有时候不是我们不想做单元测试, 而是这代码写的实在是没法测试.... 举个例子, 如果一辆汽车在产出后没完成测试, 那么没人敢去驾驶它. 代码也是一样的, 如果项目未能进行该做的测试, 那么客户就不敢 ...
- .NET Core TDD 前传: 编写易于测试的代码 一 -- 缝
转载于: https://www.cnblogs.com/cgzl/p/9365955.html 有时候不是我们不想做单元测试, 而是这代码写的实在是没法测试.... 举个例子, 如果一辆汽车在产出后 ...
- .NET Core TDD 前传: 编写易于测试的代码 -- 单一职责
第1篇: 讲述了如何创造"缝". "缝"(seam)是需要知道的概念. 第2篇, 避免在构建对象时写出不易测试的代码. 第3篇, 依赖项和迪米特法则. 第4篇 ...
- .NET Core TDD 前传: 编写易于测试的代码 -- 构建对象
该系列第1篇: 讲述了如何创造"缝". "缝"(seam)是需要知道的概念. 本文是第2篇, 介绍的是如何避免在构建对象时写出不易测试的代码. 本文的概念性内 ...
- .NET Core TDD 前传: 编写易于测试的代码 -- 依赖项
第1篇: 讲述了如何创造"缝". "缝"(seam)是需要知道的概念. 第2篇, 避免在构建对象时写出不易测试的代码. 本文是第3篇, 讲述依赖项和迪米特法则 ...
- 新书《编写可测试的JavaScript代码 》出版,感谢支持
本书介绍 JavaScript专业开发人员必须具备的一个技能是能够编写可测试的代码.不管是创建新应用程序,还是重写遗留代码,本书都将向你展示如何为客户端和服务器编写和维护可测试的JavaScript代 ...
- 编写可测试的JavaScript代码
<编写可测试的JavaScript代码>基本信息作者: [美] Mark Ethan Trostler 托斯勒 著 译者: 徐涛出版社:人民邮电出版社ISBN:9787115373373上 ...
- 从一张图开始,谈一谈.NET Core和前后端技术的演进之路
从一张图开始,谈一谈.NET Core和前后端技术的演进之路 邹溪源,李文强,来自长沙.NET技术社区 一张图 2019年3月10日,在长沙.NET 技术社区组织的技术沙龙<.NET Core和 ...
- 编写Avocado测试
编写Avocado测试 现在我们开始使用python编写Avocado测试,测试继承于avocado.Test. 基本例子 创建一个时间测试,sleeptest,测试非常简单,只是sleep一会: i ...
随机推荐
- centos网络配置方法(手动设置,自动获取)
不知道为什么最近一段时间网络特别的慢,还老是断,断的时候,局域网都连不上,当我手动设置一下ip后就可以了,搞得我很无语.下面是2种设置网络连接的方法,在说怎么设置前,一定要做好备份工作,特别是对于新手 ...
- 【译】Flink + Kafka 0.11端到端精确一次处理语义的实现
本文是翻译作品,作者是Piotr Nowojski和Michael Winters.前者是该方案的实现者. 原文地址是https://data-artisans.com/blog/end-to-end ...
- 再谈.net的堆和栈---.NET Memory Management Basics
.NET Memory Management Basics .NET memory management is designed so that the programmer is freed fro ...
- VMware12安装虚拟机教程、Ubuntu16.04安装教程(包括vmware tools的安装)
转自https://jingyan.baidu.com/article/c275f6ba07e269e33d756714.html 方法/步骤 1 虚拟机.Linux操作系统介绍及下载地址 虚拟机VM ...
- Java中能否利用函数参数来返回值
转自https://blog.csdn.net/da_da_xiong/article/details/70039532 我们在写代码时通常会遇到一种情况,就是我们可能希望在一个函数操作完成后返回两个 ...
- Dubbo原理和源码解析之服务引用
一.框架设计 在官方<Dubbo 开发指南>框架设计部分,给出了引用服务时序图: 另外,在官方<Dubbo 用户指南>集群容错部分,给出了服务引用的各功能组件关系图: 本文将根 ...
- Nginx隐藏index.php和配置vhost
nginx启动命令 启动:nginx停止:nginx -s stop退出:nginx -s quit重启:nginx -s reopen重新加载:nginx -s reload平滑启动:kill -H ...
- MySQL-5.6.36-多实例-部署(编译版)
MySQL多实例_沁贰百科 注:部署双实例前,首先需要部署单实例,单实例部署详情如下: https://www.cnblogs.com/wangqiner/p/9081002.html 1.如已经安装 ...
- Java 11 新功能来了!
关键时刻,第一时间送达! 目前 Oracle 已经发布了 Java Development Kit 10,下个版本 JDK 11 也即将发布.本文介绍 Java 11 的新功能. 根据Oracle新出 ...
- MongoDB面试题
1.什么是MongoDB MongoDB是一个文档数据库,提供好的性能,领先的非关系型数据库.采用BSON存储文档数据.BSON()是一种类json的一种二进制形式的存储格式,简称Binary JSO ...