单元测试不仅是软件行业的佳实践,在敏捷方法的推动下,它也成为了可持续软件生产的支柱。根据新的年度敏捷调查,70%的参与者会对他们的代码进行单元测试。

  单元测试和其他敏捷实践密切相关,所以开始编写测试是组织向敏捷转型的踏脚石。道路漫长,但值得去做。我将在本文介绍符合要求的小技巧,以及在开发周期里进行单元测试的步骤。

  有效的单元测试默认要能自动化。没有自动化,生产力会下降。没有自动化,单元测试的习惯也不会持续太久。依靠手工测试(由测试人员或开发人员完成)并不能持续太长时间;在有压力的情况下,没人会记得去运行所有的测试,或者去覆盖所有的场景。自动化是我们的朋友,所有的单元测试框架都支持自动化,而且集成了其他自动化系统。

  单元测试对现代开发来说至关重要

  有代码相关的测试,我们有一个天然的安全保障。我们修改的代码要是带来了什么问题,测试会告诉我们。这个安全保障越健全,我们对代码正常运行的信心越大,对按需修改代码的能力也越有信心。

  和其他类型的测试相比,单元测试的主要优点是反馈迅速。在几秒钟内运行数百个成套的测试,这对开发流程很有帮助。我们会形成“添加一些代码,添加测试,测试运行通过,前进”的节奏。小步前进、确保一切正常也意味着调试时间会大大减少。测试能提高生产力也不足为奇了——在Bug上少花时间,把更多的时间用到新功能的推出上。

  依赖关系的壁垒

  给新建项目添加测试相当容易——毕竟代码不会阻碍测试。不过这种情况不常见。大多数人都是在处理遗留代码,这些代码不太容易测试,有时候甚至运行不起来——它需要的数据或配置可能只存在于生产服务器上。我们或许要为不同的场景创建不同的设置,这也许会花费过多的精力。在很多情况下,我们可能还会为了测试修改代码。这让人无法理解:我们编写测试是为了能有修改代码的信心,还没有测试又该如何去稳妥地修改代码呢?

  代码可测性是语言和工具的功能。大家认为Ruby等动态语言是可测的。对于测试的内部代码,我们可以改变其依赖关系的行为,而不用修改生产代码。C#或Java等静态类型语言则不太容易去测试。

  下面有个例子:一个C#的过期检查方法,检查是否超过了特定日期:


public class ExpirationChecker
{
    private readonly DateTime expirationDate = new DateTime(2012, 1, 1);

    public bool IsExpired()
    {

        if (DateTime.Now > expirationDate)
        {
            return true;
        }
        return false;
    }
}
 


  在这个例子里,IsExpired方法的DateTime属性对测试运行时间有强依赖。Now返回的是实际时间。这个方法有两种情况,它会根据日期返回不同的值。修改计算机时间是不行的,因为我们要在任何时候到任何计算机上去测试场景,并且不能带来任何副作用。

  要测试到两种情况,一种可能的解决方案是修改代码。比如说,我们可以把代码修改成:


public bool IsExpired(DateTime now)
{
    if (now > expirationDate)
    {
        return true;
    }
    return false;
}