职责链模式的别扭就像用门框夹核桃

职责链模式

责任链模式在面向对象程式设计里是一种软件设计模式,它包含了一些命令对象和一系列的处理对象。每一个处理对象决定它能处理哪些命令对象,它也知道如何将它不能处理的命令对象传递给该链中的下一个处理对象。该模式还描述了往该处理链的末尾添加新的处理对象的方法。

以上是wiki对职责链模式的定义。

举个例子来说,我们的系统中需要记录日志的功能。日志需要根据优先级被发送到不同的地方。

低优先级的日志输出到命令行就好了。而高优先级的错误信息则需要通过邮件通知相关人员并且输出到命令行。

这个例子也是来自wiki的。

以下是wiki提供的Java实现:

Java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
abstract class Logger {
    public static int ERR = 3;
    public static int NOTICE = 5;
    public static int DEBUG = 7;
    private int mask;

    private Logger next;

    public Logger(int mask) {
        this.mask = mask;
    }

    public void setNext(Logger logger) {
        next = logger;
    }

    public void message(String msg, int priority) {
        if (priority <= mask) {
            writeMessage(msg);
        }
        if (next != null) {
            next.message(msg, priority);
        }
    }

    abstract protected void writeMessage(String msg);
}

首先定义一个Logger抽象类。从其setNext和message这两个方法可以看出,我们后面会把多个具有不同writeMessage实现的Logger链到一起,并且依次让它们处理某件需要被记录的事件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class StdoutLogger extends Logger {
    public StdoutLogger(int mask) {
        super(mask);
    }

    protected void writeMessage(String msg) {
        System.out.println("Writing to stdout: " + msg);
    }
}

class EmailLogger extends Logger {
    public EmailLogger(int mask) {
        super(mask);
    }

    protected void writeMessage(String msg) {
        System.out.println("Sending via e-mail: " + msg);
    }
}

class StderrLogger extends Logger {
    public StderrLogger(int mask) {
        super(mask);
    }

    protected void writeMessage(String msg) {
        System.err.println("Sending to stderr: " + msg);
    }
}

然后有三个Logger的实现,分别为向命令行输出消息,发送邮件(当然是假的),向命令行输出错误。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class ChainOfResponsibilityExample {

    private static Logger createChain() {
        Logger logger = new StdoutLogger(Logger.DEBUG);

        Logger logger1 = new EmailLogger(Logger.NOTICE);
        logger.setNext(logger1);

        Logger logger2 = new StderrLogger(Logger.ERR);
        logger1.setNext(logger2);

        return logger;
    }

    public static void main(String[] args) {
        Logger chain = createChain();
        chain.message("Entering function y.", Logger.DEBUG);
        chain.message("Step1 completed.", Logger.NOTICE);
        chain.message("An error has occurred.", Logger.ERR);
    }
}

最后,有一个main函数,创建三个Logger的实例,把它们通过setNext链在一起。 只需要调用一次message就可以让三个Logger依次工作。

如果以后再有更多的Logger呢,还是可以通过同样的方式把它们链接起来协同工作。

很好,很强大,很易于扩展,对吧?

不过再想一下

这三个Logger的实现类看起来都非常的单薄,弱不禁风。

一个接收mask的构造函数,其唯一职责就是把接收到的mask传递给父类的构造函数。

然后父类根据mask和所发生事件优先级的大小关系决定到底要不要调用子类实现的writeMessage方法。

也就是说,子类完全没有定义自己的实例级状态,其实例级方法的行为也就谈不上随着其状态的变化而变化了。

换句话说,这几个子类存在的价值就在于为父类提供writeMessage这个函数。

啊。。。。。。!

一说到提供函数,我就想到了。。。。。。

functions

我想到的自然是FP了,既然需要的是函数,那我们就使用函数好了。

何必用更重的抽象手段:类,去包裹函数呢?

下面就是比较偏函数式的Scala实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
object Loggers {
  val ERR = 3
  val NOTICE = 5
  val DEBUG = 7

  case class Event(message: String, priority: Int)

  type Logger = Event => Event

  def stdOutLogger(mask: Int): Logger = event => handleEvent(event, mask) {
    println(s"Writing to stdout: ${event.message}")
  }

  def emailLogger(mask: Int): Logger = event => handleEvent(event, mask) {
    println(s"Sending via e-mail: ${event.message}")
  }

  def stdErrLogger(mask: Int): Logger = event => handleEvent(event, mask) {
    System.err.println(s"Sending to stderr: ${event.message}")
  }

  private def handleEvent(event: Event, mask: Int)(handler: => Unit) = {
    if (event.priority <= mask) handler
    event
  }
}

这个代码已经简短到我不想解释的程度了。不过还是解释一下吧。

三个log的的等级ERR,NOTICE和DEBUG和之前Java的实现是一样的。

一个case class Event,用来包裹需要被log的事件。

type Logger则是声明了一个函数签名,凡是符合这个签名的函数都可以作为logger被使用。

然后便是三个函数实现,它们将mask通过闭包封进函数内。这三个函数共同依赖一个私有handleEvent函数,其作用和Java代码中的message类似,判断mask和正在发生的事件之间优先级大小关系,并以此决定当前logger是否需要处理该事件。

哎?等一下,这个是职责链模式啊,那个啥,链在哪儿呢?

很简单,这样就可以链起来了:

1
2
3
4
5
6
7
8
9
10
11
12
object ChainRunner {

  import chain.Loggers._

  def main(args: Array[String]) {
    val chain = stdOutLogger(DEBUG) andThen emailLogger(NOTICE) andThen stdErrLogger(ERR)

    chain(Event("Entering function y.", DEBUG))
    chain(Event("Step1 completed.", NOTICE))
    chain(Event("An error has occurred.", ERR))
  }
}

以上代码中的andThen就可以把三个logger链在一起。

这个andThen是个什么东西?何以如此神奇?

欲知详情,请参考我之前的另一篇博客: http://cuipengfei.me/blog/2013/12/30/desugar-scala-9/

而链接之后的结果本身也是一个函数,于是我们就可以调用chain并传入Event了。

这份代码和前面Java版的行为是等价的,输出是一致的。

门框夹核桃

最后回到标题上去:门框夹核桃,意即用不合适的工具解决问题。

职责链模式想要做到的事情其实就是把多个函数链起来调用。

该模式提出的时候FP并不如今日盛行,其作者选用类来包装需要被链接的多个函数,这无可厚非。

无论是class,还是function,都是为程序员提供抽象的手段。当我们想要链接的东西就是多个function,选择直接用function而非class就会显得更加自然,也更加轻量且合适。

当年design pattern的作者广为传播各种patterns,实为功德。

不过今天我们有了核桃夹,就无需一定要用门框了。

最后,依照惯例,羞辱Java一小下下。 以上wiki提供的实现有77行,偏FP风的实现只有38行,只有一个实体Event。

Share

Selenium是一瓶陈年佳酿

WE_Logo_FullColor_Stack_copy

软件质量传道士——Anand Bagmar是一名具有丰富实际经验并以结果为导向的软件质量传道士,他在IT领域拥有超过17年的经验,其中有超过14年专注于测试领域。他热衷于交付一个高质量的产品,同时致力于自动化测试工具、架构和框架。Anand撰写测试相关的博客文章并开发了软件测试相关的开源工具——WAAT (Web Analytics Automation Testing Framework), TaaS (for automating the integration testing in disparate systems) 和 TTA(Test Trend Analyzer).

起源

敏捷方法论始于2001年。随着越来越多的团队开始理解到更多敏捷宣言之外的内容,他们也开始识别出真正敏捷所必需的不同实践。

测试自动化是团队在工作中做好敏捷的一个必要的实践。测试自动化看似简单,但实际上为了让它真正地具有实效,团队不仅需要在流程中遵守很多规则,而且需要选择既能提供快速反馈又能满足测试自动化扩展的合适工具和技术。

必须要说的是,即使到现在,在同一个迭代中,伴随开发工作完成全面的自动化测试,对很多团队来说都是一个巨大的挑战。如果测试自动化采用了那些难以适应被测产品快速改变的工具和技术,这种无力感就更强烈了。

2004年Jason Huggins在ThoughtWorks测试一个内部应用时,创建了一个JavaScript的测试工具;而这个工具改变了浏览器(基于浏览器的测试)自动化测试的方法。这个工具后来慢慢进化成开源的“Selenium”。如果感兴趣,可以参考“Selenium历史”了解更多的演变历史。

不光以前,现在依然有很多非开源的、商业的测试工具和产品,而这些工具的卖点都是在难以说明的复杂过程中自动地做自动化测试。很遗憾,想要使用这些工具和产品都需要付出非常高的授权使用费。

这样导致的结果就是,各种机构和团队为了控制成本,只允许一小部分人来使用,来限制这些产品的大规模使用。而这就进一步导致了团队间的隔离。

开源的Selenium挑战了这种工作方式,并且打破了团队间的藩篱。

所有知道如何编程的团队成员(QA,开发人员以及其他成员)现在都能为搭建和运行自动化测试贡献自己的力量,而不需要考虑授权的费用。

更重要的是,开发人员和QA现在能对测试实现有同样的认知,从而减少了做出错误假设的可能。

现在距离Selenium推出已经有十年了,这十年之间发生了很多事。在技术领域中,基于测试的工具和技术发生的变化更多。而Selenium不仅仅存活下来,,而且随着时间变大变强,这实属不易。

如果把Selenium比作一瓶酒,十年时间足够让它变成一瓶陈年佳酿吗?

Selenium目前在业界的声誉:

  • 现如今,对于做基于浏览器的测试的人来说,不了解或者说没听说过Selenium是相当罕见的了。
  • 目前有许多其他用不同语言实现的框架都是基于Selenium的,这为测试自动化提供了更多的可能。
  • 有许多具有创新力的公司都基于Selenium开发了不同的产品,为它们的客户提供了更多的价值,比如说SauceLabs和BrowserStack等。
  • 全球的服务型组织都会对潜在客户的进行管理层面的对话或者提供基于Selenium的服务。
  • 近年来涌现出大量组织来销售基于Selenium自动化的测试服务。
  • Selenium是求职的热门搜素技能之一(关于测试自动化)
  • Selenium团队也在努力快速地推出更多的功能,并且尽量标准化对浏览器和移动浏览器的自动化支持。

为什么Selenium如此流行呢?

所以,到底为什么Selenium已经存在了10年但还能越来越强大和流行呢?其实,原因很多,最重要的原因是——它就是简单有效,它能够实现API所宣称的能力。

它是开源的——所以你可以深入了解它的工作原理,如果有需要的话,社区的每个人都能力去改进它。而你不需要成为同类产品“白金级”用户,就可以接触到它的核心工作原理。

社区支持很强大。如果Selenium核心贡献者不能及时回答提问,社区里还有大量的专家可以提供支持。

很可能有人也遇到过类似的问题,所以思路/建议/变通的方法就有很多。除此之外,功能修复一般都很快。

它是免费的——所以只需要拥有良好的编程语言专业知识,不需要在工具上破费。

由于敏捷的广泛发展,基于Selenium的自动化满足了大多数团队对于测试自动化的需求。

Selenium是测试自动化的银弹吗?

和其他工具和技术一样,你需要在正确的场景下使用Selenium。要不然,你不仅会渐渐失去工作的价值,而且会面对更多的挑战。

随着敏捷方法论不断成为主流,对更好更快的测试自动化的需求也变得很明显。

其他原因之中有一条,Selenium IDE可以更好地让不了解Selenium具体如何工作的人,通过使用录制得到脚本和浏览器进行交互。可是,和其他录制的工具一样,你得保证录制的脚本在所有情况下都工作,而且你要能够智能地使用不同类型的数据(有需要的话),处理等待和超时。

遗憾的是,不少使用Selenium IDE来录制脚本的人开始自称Selenium专家。

基于Selenium的自动化看似是所有自动化测试问题的正确的解决方案。它开始成为自动化的银弹。可是,世界上并没有这样的银弹。我们忽略了测试金字塔有多层,每层代表的一种类型的测试都是待测产品有必要的。

理想的测试金字塔看起来的样子:

01

对于基于Selenium(也包括其他UI自动化测试工具)的测试,要聚焦于金字塔的顶部,而不是试图覆盖整个金字塔的各个层级。这也正是这类测试的真正价值所在。

通常,那些对于测试自动化框架应该如何构建完全没有概念,或者只有有限概念的人,成为了测试自动化的主管和测试架构师。更加糟糕的是,层层传递的压力和数量导向的测试以及测试自动化,导致只是快速地添加自动化测试,而并没有审慎地思考和规范的实践。

所以很快地,理想化测试金字塔变成了下述的反模式之一:

02

结果就是这颗银弹变成了一颗致命的子弹,导致团队对于自动化、自动化的结果和团队的成员都丧失了信心。

很多团队的情况都是:虽然有无数的基于Selenium的自动化测试,并且团队的绝大多数时间都耗费在维护和修复测试上,而且还会有专门的团队针对产品执行手动的回归测试。这无疑是一种双输的局面,但是却是非常真实和常见的现象。从而导致了团队中成员的相互指责。

至为重要的一点是,我们要构建鲜活的、并能随着被测产品用户案例不断进化的自动化测试框架 。自动化测试框架必须要可维护和可扩展,这是作为测试企业级产品的不言而喻的需求之一。同时,识别那些可以通过浏览器实现自动化的测试也是非常重要的。

那么,接下来会发生什么?对你来说意味着什么呢?

测试的未来非常有前景。每一分钟,我们都面临着设备过剩和技术发展;而对于那些没有走上学习和适应科学和技术趋势之路的人来说,这是非常可怕。

以下是一些可以让你沿着这种趋势前行的想法:

  • QA的职责变得更加复杂,并且需要思维和技术纪律的转换
  • 参与和理解产品架构,并且对其作出贡献;这有助于理解需要测试什么,需要在测试金字塔的哪一层级实现自动化以及为什么要这么做。
  • 了解测试自动化是软件开发的一种形式。精通开发实践的同时,应用测试技巧会帮助你做好测试自动化。
  • 网页测试主导了过去十年测试的发展,现在的QA不仅必须了解如何自动化地测试网页,还必须了解如何自动化地测试移动和交互性的应用。
  • 行为驱动测试(Behavior-Driven Testing,BDT)作为一种可以识别并构建有效的回归测试用例集的方法,可以帮助规避我们之前提到的那些反模式
Share

合理救灾的四点建议

Help Support Advice Assistance and Guidance on a signpost

于2013年5月首发于《公益时报》个人专栏
仅在过去的两周内,接连发生了波士顿爆炸案和420雅安七级地震,自然灾害或是人为灾害令我们的世界处处充满恐慌——04年印度洋海啸造成30万人伤亡;05年南亚地震,08年汶川地震,2010年日本海啸以及玉树地震共掠走超过20万无辜人的生命。在这些血淋淋的数字背后教会我们人类应对自然灾害的不同经验教训,尤其在此次雅安地震后,公众和公益组织对救灾的反应充分体现了中国公益行业的变化和进步,而从我的角度仅提出对日后合理救灾的四点建议:

准确数据第一时间及时披露
雅安地震爆发后,民间力量再次凸显——不到一周时间,截至到25日,各基金会筹款总数即达到了4.3亿(数据待更新,不包括物资捐赠)。但是我们没能做到对于每个乡镇村的需求和物资到位匹配情况对外界及时公布,从而导致网络中志愿者零散信息成为主流信息,传播的分解信息对公众成为误导。由于没有准确的评估信息以确保捐赠和灾区所需完全匹配,超过基金会承受能力的物资和财务捐赠势必带来资金正确使用压力和物资过剩。

尤其是作为企业来说,应该根据灾难的级别合理调整自己的捐赠数额,而不是一味追求“捐款比金额”,“捐的越多越体现企业实力”,“超过竞争者的捐赠数”,——追求捐赠,而不过问灾难评估,不追踪资金使用,企业此类畸形心理只能造成公益组织的膨胀,为救灾部署带来压力。

专注于物流管理与供需对接
从雅安地震来看,民众获得信息的渠道多通过新媒体的零散信息,加上各界人士无论以个人或以组织名义纷纷前往一线,一度造成灾区交通阻塞,救灾工作难以有序进行。政府部门救灾的两条线管理,即一面将数据搜集的信息上报,另一面根据需求进行匹配,但是由于诸多客观原因,数据搜集不能精准到村级单位,不能完全保证救灾无死角,否则也不至于出现救灾物资整齐地排在后方而不能及时提供给某些受灾居民。

理想做法应该是物资的县城一级的单位需要进行仓储和分解,根据每天不同的动态变化信息进行搜集和公布,将搜集的大型物资进行仓储管理和分类后要随时出库入库,保证有强大物流体系,乡镇的交通和物流成为关键。

救灾物资的优先级渠道管理
当灾难肆虐来临时,来自四面八方的各界人士在第一时间伸出援助之手,但是要知道所能提供捐助的不同类型自然有不同的价值,并非有所物资为灾区所需。虽没有准确国内案例证明如何处理过剩物资,积压或是销毁都是捐助人不愿看到的!美国卡特里娜飓风期间,提供给几千人的速食食品造成积压,最后因过期不得不销毁。我们应该从这样的案例中明白,应该将迫切需要的如食品,药物等物资提前供应,需要保证供给临时避难的物资有序发放,而不是造成最终浪费。

充分调动公益组织的执行力
此次救灾中420救援队的出现无疑是雅安赈灾的最大亮点——14家公益机构仅在灾难发生数小时内自愿组成420救援团队,从筹资、外联、物资采购和发放、灾情信息搜集、志

愿者管理、仓储还是统筹运营,一切工作根据公益组织各自所长按部就班,经过时间的洗礼和历练,公益组织的此次成功合作一定是中国公益发展具有里程碑的一幕。也许在救灾一线,公益组织的合作和救灾体系仍有可提升空间,但是从此次420救援队的执行力、反应速度和专业性来看都是前所未有,值得称赞!

本文转自:http://ninazhou.com/2013/06/08/%E5%90%88%E7%90%86%E6%95%91%E7%81%BE%E7%9A%84%E5%9B%9B%E7%82%B9%E5%BB%BA%E8%AE%AE/

Share

孙子兵法的智慧——拯救死亡行军

entersunzi

我们看了太多失败的和即将失败的项目,在软件领域更是如此。每天早上醒来在朋友圈里看到半夜更新的,不是身在国外的朋友,就是在办公室为了项目上线而加班的朋友。

Edward Yourdon为那种项目约束与目标之间相差一倍以上的软件项目专门写了一本书,叫《死亡行军(Death march)》。

我曾经亲眼见证过几个“死亡行军”项目的立项过程。

其中一个项目,那时候我才工作不久,一个项目从无到有,“大领导”希望在21天之后上线,这个日期包含了周六和周日。但是按我们的预估——当然我们也不会盲目乐观到每天只工作8小时,这个项目按现有资源起码需要3个月到4个月。在产品立项过程中,“大领导”坚决不肯让步,时间、范围均压得死死的。就这样,我们在会议室里博弈了整整两天,最后的结果是我忧心忡忡地走出会议室,问我身边的同事:你觉得咱们能办到吗?同事的表情我至今印象深刻,那是一种释然和轻松——他笑着说:怎么可能嘛。

最后的结果,项目延期了半年。


如何能够让自己的项目稳定运行并得到期望的结果呢,避免死亡行军,打一场胜仗呢?

几千年前的孙子兵法已经给我们提供了思路:

故知胜有五:知可以战与不可以战者胜,识众寡之用者胜,上下同欲者胜,以虞待不虞者胜,将能而君不御者胜。

我们只做高价值的事情

价值驱动作为敏捷开发的两根支柱之一,为软件开发提供了一种全新的思路。在上世纪八十年代之前,软件开发的时候还有这样的等式:功能=价值。
所以奇怪的一幕出现了:软件开发这个行业一边承受着巨大的进度压力,一边在生产着没有人用的功能。

微软曾经发起office 2003用户调查,希望调查用户希望 Office 2007实现哪些新特性,结果收获了让人吃惊的消息。产品总经理 Takeshi Numoto 如是说:“超过90%的特性请求早已存在于 Office 中可供使用”。

更何况对于所有的软件开发组织来讲,资源不足都是常态,识别出来哪些可以不做,我们才知道应该去做什么。

知可以战与不可以战者胜。

通过限制在制品的方式来缩短前置时间

对于缩短产品交付周期,一向是很多客户都会提到的问题,特别是电信级的产品,动辄一年的交付周期。当前市场环境已经变得越来越快,根据波士顿咨询公司的调查,最近的20年已经成为变化最剧烈的20年。

22484-8eb1d728c83ca707

《经典战略工具过时了?BCG矩阵新解》

在这样的市场环境下,“响应速度”就被提到越来越高的位置上,而衡量响应速度最直接的指标就是“前置时间”(lead time)。一个需求从产生到变为现实,究竟可以有多短?

利特尔法则是一个神奇的公式,它简单到甚至可以被10岁的孩子所理解:平均吞吐率=在制品数量/平均前置时间。对于一般的组织来讲,平均吞吐率约等于团队的产能,把它视作常量的话,我们想得到更短的平均前置时间,就需要控制在制品数量,小批量地快速流动。

要么不做,要么快完。

识众寡之用者胜。

团队所有人目标一致

“我们所处的项目,目标是什么?”有没有保证项目有着一个清晰而具体的目标?有没有保证所有成员都能够理解并执行?

在客户现场的时候,我经常听到的问题是:“作为一个管理者,我如何能够准确看到员工的人员利用率?你有没有办法让团队里所有的人都忙起来?”每听到一次这个问题,我在心里就会默念:“你可以让闲着的人都出去跑圈啊,这样大家都忙起来了。”

软件开发的项目,绝大多数(保守了,其实可以说“所有”)的目标都不是“所有人都忙起来”,而是“快速交付价值”。也就是说我们做任何的努力,都是为了能够让需求尽快上线。

而敏捷开发中的各种可视化手段,包括故事墙、故事地图,都起到了这样的作用——目标可视化。特别是树立在团队中间的物理看板,我们真正的目标都清晰地挂在上面,所有人的工作和努力,都是在推动着一张张卡片从板子的左边挪向右边,如果你做的事情对于推动卡片没有起到直接或者间接的作用,那就要好好思考一下了:我们的目标究竟是什么?

至于说人员利用率——谁知道一个正在端着咖啡面对窗外发呆的知识工作者(译作Thought Worker)是不是在为接下来消灭更多卡片而在布局呢?

上下同欲者胜。

做好风险管理

在客户现场待了一年,看的项目虽然不多,但也发现了一些共通的问题,其中最大的一个就是风险管理。

有不少项目在前期根本就没有做风险识别,或者识别的风险缺乏跟踪,到项目执行中仍然会像放炮仗一样一个接一个的爆炸。其实风险管理作为PMBOK九大知识领域之一,管理和应对手段都很成熟了。

应对的思路就有“转移、接受、缓解、消除”四种,而我看到的情况通常都以“接受”为应对手段,赌这个风险不会发生,或者等一个Risk变成了Issue再去想应对方案。

风险识别不论是在需求优先级排序还是在架构设计上都是非常重要的一环,我们之所以把高价值、高风险的需求视作第一优先级,是因为我们希望尽早识别风险,尽早释放风险。我们提的“简单设计”简单到什么程度,也是由是否能够涵盖最大的风险作为边界的。

以虞待不虞者胜。

团队应当自组织,领导者应当负起管理者的责任

客户现场另外经常被提及的问题就是:“如何打造自组织团队?”“团队自组织了,我们经理怎么办?”

我的答案通常是:团队自组织应当是一个逐步打造的过程,而领导者更应该负起管理者的责任。《管理3.0》中提到,授权有七个级别:从不信任到信任,或者说团队成熟度从低到高分别是:告知→贩卖→咨询→商定→建议→征询→委托。

作为管理者,打造自组织团队的过程也是逐步团队赋能的过程,团队成熟度不高而采取放任的做法,对于自组织的打造百害而无一利,反之亦然。所以作为管理者,最重要的责任就是在对团队赋能,当上面的四条团队都掌握了玩法,管理者就可以采取更高层次的授权了。

而所谓自由,就是团队的事务管理者不要管,团队外的事务管理者尽量多去做。区分管理者水平高低的点就在于如何判断一件事属于团队“内”还是“外”。

将能而君不御者胜。

我们总结一下

故知胜有五:知可以战与不可以战者胜,识众寡之用者胜,上下同欲者胜,以虞待不虞者胜,将能而君不御者胜。

 

Share

当谈论覆盖率时我们在谈什么?

80-20-Rule

代码覆盖率 vs. 测试覆盖率

代码覆盖率通常指跑完测试后, 由工具自动统计的在跑测试的过程中被测代码的覆盖率, 细分的话包括语句覆盖率, 分支覆盖率, 函数覆盖率等. 由于代码覆盖率可由工具自动产生, 采集成本非常低, 而又比较直观, 所以历来受到开发团队及管理者的欢迎, 有的组织甚至将其作为 KPI 指标之一.

然而围绕着代码覆盖率, 有很多有趣的事情, 尤其是将其作为 KPI 的时候. 你会发现, 长期在低位徘徊的代码覆盖率, 突然之间会有一个比较大的提升. 究其原因, 是开发团队在短时间内加了”测试”. “测试”是打引号的, 因为当我们近距离观察这些”测试”的时候, 会发现通常是调用了某个高层的入口函数, 因而牵出很多底层函数, 覆盖率就上去了, 然而, 没有一个断言(assertion), 或者是区区几个断言. 也就是说, 把产品跑了一遍, 但没有判断其行为是否符合预期, 而代码覆盖率突然就达标了.

尽管对于追求自我改进的团队来说, 不会这么掩耳盗铃, 代码覆盖率依然是有价值的反馈指标, 但这从侧面说明了代码覆盖率并没有表达出我们对于外部质量真正的关注点. 那么我们对于质量真正的关注点是什么呢?

是断言的覆盖率, 即测试覆盖率. 换句话说, 我们真正关心的是, 我们总共应该有多少测试用例/验收条件/检查点, 它们中有多少已经被覆盖了, 即做出了真正的断言. 但目前为止, 还没有工具能自动统计跑完测试后, 测试覆盖率是多少. 代码覆盖率仅仅是无法自动统计测试覆盖率时的一个替代品.

为了统计测试覆盖率, 需要准备分子和分母的信息. 分母是产品”完整”的测试用例列表, 分子是已经执行的测试用例列表, 包括手工和自动. 如果你关心测试覆盖率, 而手头又没有这两个东西, 就要开始准备了.

注1: 利用现有的 xUnit 测试框架, 可以在某种程度上得到测试覆盖率. 比如可以将”完整”的测试用例列表用 xUnit 的测试用例表达出来, 其中对于还没实现的, 设置为 ignore. 这样可以从最后的报告中看出总数, 和 ignore 的数量(当然如果你不做断言, 还是白搭). 现在更多的是借助管理工具甚至 Excel, 来手工维护”完整”的测试用例列表及状态. 如果你知道有更好的方式, 请告诉我.

注2: 前面”完整”的测试用例列表, “完整”一直打着引号, 因为这是一个无法证明的问题, 我们只能根据经验设计测试用例, 无法保证其完整性, 并且随着产品的开发, 这个列表也会动态更新. 至于如何让测试用例尽可能完整, 是组织应该投入的地方.

此测试覆盖率 vs. 彼测试覆盖率

基于前面的描述, 那么当我的测试覆盖率达到某个比较高的数值, 比如80%, 是不是我就可以比更低的数值比如20%, 对产品更有信心呢? 答案取决于你的测试用例的设计.

我们都听过80/20原则. 比如用户80%的时间在使用20%的功能, 20%的功能就可以支撑起用户最关键的业务场景. 那么, 如果80%的测试覆盖率, 覆盖的是那不常用的80%的功能, 而20%的覆盖率, 覆盖的恰恰是最常用最关键的那20%的功能, 那么, 你是否还像开始那样, 相信80%的覆盖率带来的安全感呢?

基于测试覆盖率很难达到100%这个前提, 基于我们的发布时间总是很紧张而又要保证质量这个前提, 我们必须投入精力, 做测试用例的价值分析, 挑选出最有价值的测试用例, 优先安排资源实现和运行.

如果团队的测试用例没有经过价值分析, 没有优先级划分, 那么这就是接下来马上应该做的事. 这牵扯到一个问题, 测试人员及测试技能的价值.

当我们谈论测试技能时我们在谈什么

最近几年随着自动化测试框架的流行, 评价一个人员测试能力的标准逐渐变成了是否能写自动化测试. 如果照这个标准, 所有的开发人员一夜之间都具备了合格的测试能力. 这显然是一个不成立的结论.

测试至少分测试用例的设计和测试用例的编写执行两部分. 自动化测试的长处仅仅在于编写执行. 使用自动化测试框架并不会自动让我们的测试更有效, 更完备, 更具洞察力. 而测试的有效性和完备性, 通常是我们更关注的. 然而遗憾的是, 通常组织中这方面的知识比较欠缺, 关注度不够, 技能交流较少.

如果我们交流测试知识时, 更多的是谈论 xUnit, RobotFramework等, 而不是等价类/边界值, 恶邻测试法/快递测试法, 关键路径分析等, 那几乎可以肯定我们遗漏了更重要的东西.

要在时间资源人力资源有限的情况下保证产品质量, 我们需要提高测试用例的设计能力, 价值分析能力, 安排合理的测试策略.

本文转自:http://liguanglei.name/blogs/2015/06/01/code-coverage-vs-test-coverage/

Share