今天你写了自动化测试吗

一艘货轮满载着货物从港口启航,向浩瀚的大海深处破水而去。海面平静,微微皱起波浪,从容而显得宽容。然而,货轮的步履却有些蹒跚,发动机“轰轰轰”地嘶吼着,不堪重负,却无法让船只游得更快,倒像是海水咬住了船底往下在拖曳。

“嘟——嘟——嘟”,突然警报声响起,甲板上变得喧闹起来,一个水手模样的年轻人声嘶力竭地呐喊:“船超重了,快快快……快卸货!”声音急迫,甚至能听到哭音。然后,又是一阵喧嚷,似乎是在争吵甚么,就看到一个胖胖的中年人冲了出来。看他那肥胖的体型,真难想到他的身手竟然如此敏捷,如海豹一般破开人群,两手挥舞,大声喊道:“怎么了?怎么了?”,他停下来,吼道:“我看哪个不长眼的家伙敢卸我的货!谁敢!”

船长走了过来,略带恭敬地对那中年人说道:“老板,你看,这船超载了,船身吃紧,已经发出超重警报了。倘若不减轻船的重量,这船开不了多久就得沉了啊!”

“他奶奶的,这船可真秀气啊!”中年人一边骂骂咧咧,却也知道形势紧迫,容不得自己不下决断。可是心里总存着侥幸心理,突然灵机一动,一把拉过船长,指着这艘货轮问道:“既然这船超重,那我问你,除了货物,这船上还有哪些东西占了船身的重量?”

船长一听,立刻明白老板心里的小九九,没好气地回道:“除了货物,占了这船重量的就还有人、淡水、食品,还有救生圈、救生衣、救生艇。老板你看那样不顺心,你就扔哪样吧!”

嘿,回到现实中来吧。回答问题:倘若你是老板,你会扔哪样呢?稍有理智的人,都不难做出正确的选择。——然而,为何在软件开发过程中,我却常常看到有人选择丢弃救生圈、救生衣、救生艇呢?哪怕它们的重量对于整艘船而言如同九牛一毛,却总有人存着侥幸,认为船就超了那么一点点,或许扔出几个救生圈,就能恢复重量到安全线;于是,货物得以幸存,可以避免不必要的损失了。

或许,我们没这么傻吧。那么,让我们想想。

假设将这航行比作是软件开发的过程,那么载货到达目的地,就是实现软件需求。只有交付了货物,才算是实现了价值。至于淡水、食品以及船只,就是开发的工具与环境,而救生圈、救生衣、救生艇,就是我们在开发过程中需要编写的自动化测试(单元测试、集成测试、验收测试等)。我们需要这些测试来随时检测开发功能是否有误,及时反馈,就像在航行过程中,若是有人溺水,可以用救生衣、救生圈挽回一条生命一般。

可一旦开发时间紧促,人手严重不足,进度压力山大时,我们想到了什么呢?对于我见过的多数软件团队而言,每当面临如此窘境时,首先想到的就是减少甚至不做自动化测试。有人认为自动化测试没有价值,浪费成本;有人认为自动化测试可以以后再补,先把功能完成再说;有人认为有了手动测试,就足以保障项目的质量……如此这般,自动化测试就这般被忽略了,沦落到随时可以抛弃的地位。

倘若软件开发就只有这一个阶段,没有需求变更,没有后续开发,没有软件维护。项目的代码库如树苗一般在阳光雨露下茁壮成长,没有大风狂吹,没有烈日暴晒,没有大雨倾盆,亦没有虫蚁啃啮,那自然由得它去。然而,现实世界哪有如此美好!

Michael Feather将没有自动化测试的代码称为“遗留代码”,温伯格在《咨询的奥秘》中则认为应该将“维护”工作视为“设计”工作。自动化测试是修改的基础,重构的保障,设计的规约,演化的文档。它的重要性怎么强调都不过分,然而很可惜,在很多软件项目开发中,它甚至不如“鸡肋”的地位,说放弃就放弃了,在决定当时,毫不觉得可惜。至于以后的以后,不远的未来,谁还顾得上!!?债欠下了,什么时候偿还呢?——不知道!到了催债的那天,再想办法还债吧。

鸵鸟心态害死人啊!

扪心自问,我们经历过维护的苦楚吗?体验过修改代码的烦恼吗?修复过不胜其扰的缺陷吗?答案若是肯定,那么,如果老天再给你一次机会,把选择自动化测试的权利放在你面前,作为“曾经沧海难为水”的你,你会怎么选?——所以,我想问问程序员们:今天,你写自动化测试了吗?

Share

软件系统的稳定性

软件系统的稳定性,主要决定于整体的系统架构设计,然而也不可忽略编程的细节,正所谓“千里之堤,溃于蚁穴”,一旦考虑不周,看似无关紧要的代码片段可能会带来整体软件系统的崩溃。这正是我阅读Release It!的直接感受。究其原因,一方面是程序员对代码质量的追求不够,在项目进度的压力下,只考虑了功能实现,而不用过多的追求质量属性;第二则是对编程语言的正确编码方式不够了解,不知如何有效而正确的编码;第三则是知识量的不足,在编程时没有意识到实现会对哪些因素造成影响。

例如在Release It!一书中,给出了如下的Java代码片段:

package com.example.cf.flightsearch; 
//... 
public class FlightSearch implements SessionBean {
    private MonitoredDataSource connectionPool;
    public List lookupByCity(. . .) throws SQLException, RemoteException { 
        Connection conn = null; 
        Statement stmt = null;
        try { 
            conn = connectionPool.getConnection(); 
            stmt = conn.createStatement();

            // Do the lookup logic
            // return a list of results
        } finally { 
            if (stmt != null) {
                stmt.close();
            }
            if (conn != null) { 
                conn.close();
            }
        }
    }
}

正是这一小段代码,是造成Airline系统崩溃的罪魁祸首。程序员充分地考虑了资源的释放,但在这段代码中他却没有对多个资源的释放给予足够的重视,而是以释放单资源的做法去处理多资源。在finally语句块中,如果释放Statement资源的操作失败了,就可能抛出异常,因为在finally中并没有捕获这种异常,就会导致后面的conn.close()语句没有执行,从而导致Connection资源未能及时释放。最终导致连接池中存放了大量未能及时释放的Connection资源,却不能得到使用,直到连接池满。当后续请求lookupByCity()时,就会在调用connectionPool.getConnection()方法时被阻塞。这些被阻塞的请求会越来越多,最后导致资源耗尽,整个系统崩溃。

Release It!的作者对Java中同步方法的使用也提出了警告。同步方法虽然可以较好地解决并发问题,在一定程度上可以避免出现资源抢占、竟态条件和死锁的情况。但它的一个副作用同步锁可能导致线程阻塞。这就要求同步方法的执行时间不能太长。此外,Java的接口方法是不能标记synchronized关键字。当我们在调用封装好的第三方API时,基于“面向接口设计”的原理,可能调用者只知道公开的接口方法,却不知道实现类事实上将其实现为同步方法,这种未知性就可能存在隐患。

假设有这样的一个接口:

public interface GlobalObjectCache {
    public Object get(String id);
}

如果接口方法get()的实现如下:

public synchronized Object get(String id){
    Object obj = items.get(id); 
    if(obj == null) {
        obj = create(id); 
        items.put(id, obj);
    } 
    return obj;
}

protected Object create(String id) {
    //...
}

这段代码很简单,当调用者试图根据id获得目标对象时,首先会在Cache中寻找,如果有就直接返回;否则通过create()方法获得目标对象,然后再将它存储到Cache中。create()方法是该类定义的一个非final方法,它执行了DB的查询功能。现在,假设使用该类的用户对它进行了扩展,例如定义RemoteAvailabilityCache类派生该类,并重写create()方法,将原来的本地调用改为远程调用。问题出现了。由于采用create()方法是远程调用,当服务端比较繁忙时,发出的远程调用请求可能会被阻塞。由于get()方法是同步方法,在方法体内,每次只能有一个线程访问它,直到方法执行完毕释放锁。现在create()方法被阻塞,就会导致其他试图调用RemoteAvailabilityCache对象的get()方法的线程随之而被阻塞。进而可能导致系统崩溃。

当然,我们可以认为这种扩展本身是不合理的。但从设计的角度来看,它并没有违背Liskove替换原则。从接口的角度看,它的行为也没有发生任何改变,仅仅是实现发生了变化。如果不是同步方法,则一个调用线程的阻塞并不会影响到其他调用线程,问题就可以避免了。当然,这里的同步方法本身是合理的,因为只有采取同步的方式才能保证对Cache的读取是支持并发的。书中给出这个例子,无非是要说明同步方法潜在的危险,提示我们在编写代码时,需要考虑周全。

本文原文出自:http://zhangyi.farbox.com/post/stable-software-system

Share