质量属性之可测试性战术
1.先来说一下软件可测试性的概念
可测试性战术的目标是允许在完成软件开发的一个增量后,较轻松地对软件进行测试。
2.可测试性的目的
软件开发的一切辅助性行为最终都是为了减少开发的成本尽可能保证利益,而单纯测试的目的又有一下方面:
① 在给定的产品中尽可能多地发现错误(或bug)。
② 演示一个给定的软件产品与它的需求规格匹配。
③ 使用最小的成本和努力来验证软件的质量。
④ 生成高质量的测试用例,执行有效的测试,并发布正确和有用的问题报告。
3. 可测试性的重要性
测试很重要,因为软件错误可能很昂贵甚至很危险,一个bug越长越不被发现,它就可能带来更大的隐患。此外,如果测试团队错过了准确详尽地捕捉或识别风险和软件问题,会导致一些灾难发生,那么就需要付出昂贵的代价来检测bug,所以软件漏洞或是Bug可能会导致货币和人员的损失,历史上太多这样的例子,下面我们就来看看这些经典的事例。
2015年4月,伦敦彭博终端由于软件漏洞宕机,导致金融市场上超过30万交易商受到影响。迫使政府推迟30亿英镑的债务出售。
日产尼桑汽车由于安全气囊感应探测器的软件故障,召回超过100万辆汽车。据报道,由于此软件故障导致两起事故。
星巴克咖啡由于POS系统的软件故障无法处理交易,因此被迫关闭了美国和加拿大约60%的商店。
亚马逊的一些第三方零售商看到他们的产品由于软件故障价格全部被降至1英磅,由此导致惨重损失。
4.如何提升可测试性
我们学过设计模式后,作为软工人,都知道我们编码的五大原则:单一职责原则;开放/封闭原则;里氏代换原则;接口分离原则;依赖反转原则。尤其是单一职责原则,每一个函数,对象都完成单一的职责。主函数只对其进行调用,或者传参。这样我们的可测试性就会大大的提高。
比如我们要实现拍照和播放音乐,那先定义两个功能接口
//具有照相的功能的接口 interface IPhotograph { void Photograph(); }
//具有播放音乐功能的接口 interface IPlayMusic { void PlayMusic(); }
如果我们不遵循单一原则的设计,播放音乐及拍照功能的改变都会引起变化
//实现照相、播放音乐的手机类 public class MobilePhone : IPhotograph, IPlayMusic { //拍照 public void Photograph() { Console.WriteLine("拍照片"); } //播放音乐 public void PlayMusic() { Console.WriteLine("播放音乐"); } }
class Program { static void Main(string[] args) { IPlayMusic musicPlayer; IPhotograph photographer; MobilePhone phone = new MobilePhone(); musicPlayer = phone; photographer = phone; //播放音乐 musicPlayer.PlayMusic(); //拍照 photographer.Photograph(); Console.ReadLine(); } }
遵循单一原则的设计,引发改变的只有播放音乐功能的变化
//实现播放音乐功能的音乐播放器类 class MusicPlayer : IPlayMusic { public void PlayMusic() { Console.WriteLine("播放音乐"); } }
遵循单一原则的设计,引发改变的只有摄像功能的变化
//实现照相功能的摄像机类 class Carmera : IPhotograph { public void Photograph() { Console.WriteLine("拍照片"); } }
class Program { static void Main(string[] args) { IPlayMusic musicPlayer; IPhotograph photographer; //MobilePhone phone = new MobilePhone(); //musicPlayer = phone; //photographer = phone; musicPlayer = new MusicPlayer(); photographer = new Carmera(); //播放音乐 musicPlayer.PlayMusic(); //拍照 photographer.Photograph(); Console.ReadLine(); } }
糟糕的设计会造成什么样的后果呢?让我们来设想一下,有一天我们的需求发生了变化,现有我们已经拥有了一部手机,有拍照的功能以及播放音乐的功能,满足了现有的需求,突然有一天觉得简单的拍照功能已经不能满足了,现在需要能够拍摄高清图片的照相功能,那么怎么办呢?换手机呗,恩找一个支持高清拍照功能的手机。那么好的,需求又变了,现在想要能播放高品质音乐的功能,但是新换的支持高清拍摄的手机的硬件不支持高品质音乐播放,那就要继续换手机,前提是还要支持拍摄高清照片。相信现在已经能够看出一些问题了,这两个职责无论哪一个发生了变化,你都要去改变手机,现在只有两个职责,夸张一点说,如果有十个职责呢?那么岂不是要天天换手机,要么就不满足需求变化。既然我们看到了糟糕的设计,现在我们回到单一职责上,既然你的需求是拍照片和播放音乐,那么我给你一台相机还有一台音乐播放器,哪个功能需要改变你就换哪个,以后你要换的时候也不必去考虑其他功能,只需要关心引起你自己变化的原因。如果拍照功能发生改变,我们就去改变照相机,播放音乐功能需要改变我们就去改变音乐播放器。我们不需要去考虑播放高品质音乐是不是会对拍摄高清图片的功能造成影响,在现有的需求上能做到当然可以去做,但是往往有的时候,需求不是在设计的时候发生改变,而是一定程度之后,你已经有了一定的代码量了,可能修改的开销很高,这个时候就仁者见仁智者见智。就如上述,若是将手机类拆分,则影响了底层调用的实现,也需要修改,要是调用的地方太多,那么修改的地方也会很多,改起来也不是很方便,但是当然,也有一定的手法来做这件事情,比如手机类保留,让手机类拥有一个摄像机类对象和一个音乐播放器类对象,然后播放音乐方法则调用音乐播放器类实例的播放音乐功能,照相功能则调用摄像机类实例的照相功能,这样可以在不影响原有的东西的基础上又提升了代码的可测试性