有时候不是咱们不想作单元测试, 而是这代码写的实在是无法测试....数据库
举个例子, 若是一辆汽车在产出后没完成测试, 那么没人敢去驾驶它. 代码也是同样的, 若是项目未能进行该作的测试, 那么客户就不敢去使用它, 即便使用了也会遇到“车祸”. 编程
测试从不一样的角度看能够分红不少类. 咱们首先应该保证好单元测试可以很好的进行, 只要单元测试可以很好的进行, 那么其它测试应该均可以很好的进行. app
再详细说一下:框架
在谈到软件测试的时候, 网上的文章常常举这个建造汽车的例子, 那我也拿汽车这个例子说明问题吧.函数
假设咱们须要设计并生产一辆汽车, 可能会有两种方式:单元测试
第一种是把车设计成一个复杂的总体, 把全部须要的零件都焊到了一块儿, 也能够说它只有一个大零件, 就是汽车自己. 这样作的好处就是咱们没必要花那么多时间和精力去制做发动机, 轮胎, 车窗等等这些可替换的零件了. 这么去作是有可能把汽车的设计和生产成本下降的. 可是若是汽车被长期使用, 考虑到售后及维护, 那么成本确定会很是高了.测试
若是汽车坏了, 咱们没法检测是哪里出错, 由于它是一个总体, 没法对某部分进行隔离测试; 即便咱们知道哪里有问题, 咱们仍是没法替换损坏的部分, 由于它仍是一个总体...spa
第二种方式就是正确的方式, 咱们使用可替换的零件进行设计生产, 这样就会方便测试和售后维护. 由于车里的每一个零件均可以被替换, 也能够取出来单独进行测试. 若是汽车不能启动, 那么就对每一个零件进行检查, 最后替换出问题的零件便可, 而无需像第一种方式那样把整个车扒开进行大修.设计
很明显, 正常的汽车厂商都是使用的第二种方式, 由于其具备可测试性和可维护性. 3d
软件开发这个领域和设计汽车是很类似的, 能够像第一种方式同样开发软件, 也能够像第二种方式同样开发软件.
在现实中, 有太多的开发者使用了第一种方式, 把一大堆代码和功能都放到了一块儿. 而实际上开发者们应该采用第二种方式来进行代码的设计和编写, 即便在开发初期这可能会花掉更多的时间和精力.
有的时候不是开发者不想采起第二种方式, 而是花了很大力气却发现写出来的代码仍然不能很好的进行单元测试, 因此实际问题是不知道该如何写出易于测试的代码.
仍是汽车的例子, 若是咱们怀疑汽车的电瓶坏了, 那么采用第一种方式创造的汽车就没法进行对它的“电瓶”进行单独检测, 由于是焊到一块儿的, 也没有能够用检测的插头等; 而采用第二种方式建造的汽车则能够把电瓶拿出来, 而后咱们使用电压表等专用的仪器在隔离的状况下对其进行检测.
第二种方式之因此能够进行隔离测试是由于它采用的是可替换零件, 也就是零件能够拿下来.
用专业的术语说就是第二种方式里有缝(seam). 在软件里, 什么是缝(seam)? 缝就是你能够在程序里替换行为的地方, 而不须要在这个地方进行修改. 或者说就是可让你的代码移除依赖项并建立出可用于隔离测试对象的地方.....我可能解释的不明白, 看图吧:
虚线就是缝.
因为有缝的存在, 因此咱们能够进行隔离测试:
分别使用Test Fixture和Test double来替换调用类和依赖项.
而采用第一种方式的软件就没法把代码拆出来进行测试了, 由于没法替换依赖项, 没法接入到测试环境, 也就是说没法进行隔离测试了.
没法测试的代码有一些特色:
作到这两点, 那么咱们就可使用test double(测试替身)来代替依赖项并注入到被测试类使用, 从而进行隔离测试.
下面就是一个难以测试的例子, 这个代码并不完美, 没法展现出不可测试代码全部的特色, 可是也包含了至少两个特色:
首先它的依赖项都是new出来的, 这些依赖项就有依赖于数据库的, 因此测试的话, 咱们还须要知道数据库里面特定的数据内容..这样的结果就是测试很难完成.
其次这里用到了第三方的Mapper.Map()静态方法, 这个方法也许是通过测试的而且没有反作用的, 可是也有可能不是. 并且它形成了ProductControllerHard和Mapper类之间的紧耦合.
针对第一个问题, 我想都知道怎么去处理了, 就是使用接口. 我就很少介绍了.
针对第二个问题, 使用静态方法形成了紧耦合. 若是这个静态方法是咱们本身写的方法, 咱们能够对其重构, 变成实例方法. 可是若是它来自第三方库, 而且第三方库没有提供能够依赖注入使用的版本, 那么咱们本身能够写一个包装类(wrapper)来包装该方法:
可是因为这个Mapper来自AutoMapper库, 这个库提供了IMapper接口, 因此使用IMapper进行依赖注入便可.
可测试的代码应该以下: