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

本文是第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/

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏xingoo, 一个梦想做发明家的程序员

JavaMelody监控SQL

前言 前面讲过了Javamelody的基本配置,这里简单的介绍下,如何使用Javamelody来监控JDBC以及SQL。   在网上搜索很多资料,仅有开源...

3168
来自专栏xingoo, 一个梦想做发明家的程序员

【AngularJS】—— 7 模块化

AngularJS有几大特性,比如:   1 MVC 2 模块化   3 指令系统   4 双向数据绑定 那么本篇就来看看AngularJS的模块...

2035
来自专栏前端杂货铺

服务端事件EventSource揭秘

服务端推 服务端推,指的是由服务器主动的向客户端发送消息(响应)。在应用层的HTTP协议实现中,“请求-响应”是一个round trip,它的起点来自客户端,因...

3155
来自专栏FreeBuf

解密攻击者如何利用D-Link路由器构建僵尸网络

在这篇文章中,我们将跟大家讨论我们在几台顶级D-Link路由器中发现的安全漏洞,受影响的路由器型号如下: -DIR890L -DIR885L -DIR895L...

2658
来自专栏技术之路

Http概述(一)

Http使用的是可靠的数据传输协议,因此即使数据来自地球的另一端,也能够确保数据在传输过程中不会被损坏或产生混乱。 这样用户在访问信息时就不用担心其完整性了。 ...

2055
来自专栏IT技术精选文摘

文件句柄与文件描述符

1.概述 在实际工作中会经常遇到一些bug,有些就需要用到文件句柄,文件描述符等概念,比如报错: too many open files, 如果你对相关知识一无...

5106
来自专栏大内老A

WCF如何克服HTTP传输协议的局限提供对不同消息传输模式的实现

WCF采用消息作为通信的唯一手段,它支持不同的消息交换模式(MEP:Message Exchange Pattern),比较典型的有以下三种MEP:One-Wa...

1846
来自专栏跟着阿笨一起玩NET

asp.net中使用swfupload上传大文件

转载:http://www.cnblogs.com/niunan/archive/2012/01/12/2320705.html

1004
来自专栏一个会写诗的程序员的博客

Cookie 和 Session 机制原理分析 & 区别对比

Web application servers are generally "stateless":

1412
来自专栏坚毅的PHP

my linux FAQ

用命令查询系统是32位还是64位 getconf LONG_BIT or getconf WORD_BIT 例如: [root@sy02 /]# getconf...

3363

扫码关注云+社区

领取腾讯云代金券