阅读更多
单元测试的重要性不言而喻,但是对于经常会出现“失败测试”的现象我们该如何解决呢?失败的原因在哪?单元测试又有哪些副作用?作者Thomas Klambauer在此以最佳实践为例讲述如何提升自动测试的质量。

原文内容编译如下:

我们超过10K的单元测试大部分都是用JAVA的JUnit编写,并且用gradle自动构建工具运行。当我们添加越多的测试用例,就越经常遇到单元测试执行不稳定的问题。新添加的测试代码影响了现存的测试的执行。我们的”失败测试“(failed test)标准在它开始增加前一直表现地很不错。显然我们应该去抱怨那些糟糕的程序代码。但经过仔细的分析,我们发现造成不稳定的测试结果的真正原因。大多数由新测试造成的问题都是由于那些测试用例对测试环境作了一些不利的影响,因此也影响了其他测试的执行。

在这篇文章中,我会展示我们是怎么找出这些特定的失败测试的根因,并且由此得出的对测试环境友好的单元测试设计的最佳实践。

这一切都因为缺少单元测试时间

上个星期,我们发现一组测试没有毫无预兆就失败了。我自愿参加分析。

一组验证ThreadPool实现的重要且必须行为的单元测试。特别是,定时任务必须在特定异常抛出后继续运行。为了测试它,必须要有另外一个定时任务,抛出特定的异常。然后测试会在第一次因为异常失败后等待第二次执行。

在某些机器上运行这些测试,它们会在重新运行时超时。尽管有各种各样的异常处理器可以记录异常,但没有任何输出。只有这样一条消息输出:
Exception: java.lang.NullPointerException thrown from the UncaughtExceptionHandler in thread “pool-unit-test-thread-1″

然后,在Eclipse中运行时,这种情况永远都不可能重现,它只会出现在我们使用gradle来跑这些test的时候。

下一步:我配置gradle打开调试端口,然后使用eclipse连接以确定原因。这才发现NullPointerException是在gradle的某处代码中抛出。我下载了源代码,发现System.getProperty(“line.separator”)返回null,并且被取消引用。

有了这些信息,我检查代码,并且迅速发现另外一个校验不同平台上的字符串格式的测试在修改line.separtor属性时有不良影响。在测试完成后调用的System.clearProperty(“line.separator”),它无意中把该属性设置为null。在这种情况下,当下个测试运行时,由于它使用System.setOut重定向到控制台输出,日志信息会被写出到控制台,gradle代码会被调用。这种顺序的调用会导致gradle在获取line.separator属性值是抛出NullPointerException。注意,执行的顺序很重要,因此在ThreadPool测试时只是偶尔会出现。

通常一系列的处理器都可以捕获到它,但由于它们也写出到控制台,当它们遇到NullPointerException时,会向上抛出。

这个错误的快速的解决方式是去除clearProperty这个调用,而换用上一个line.separator属性的值。但我们是否可以做到更好呢?

单元测试的副作用

上面的部分错误是本来并且应该由测试自动化去处理的,JVM通常会重用同一个项目内的对象。因此理想情况下的单元测试应该对测试环境没有副作用,可以避免类似的错误。

测试环境包括各种可以影响之后执行的测试的资源,如在文件系统中创建文件或者上面的情况,修改java系统属性。对于文件类的,Junit提供了TemporaryFolder规则用于创建临时文件,所以我们这里开发了一个属性的类似机制。

解决方案:单元测试中恰当使用系统属性

通常情况下,假设我们要开发一个测试用例,需要设置Java系统变量为一个特定值,可能会如下:

Test.java
@Before
public void setup() {
  System.setProperty("prop", "true");
}
@Test
// use that prop.

但当其他在其之后运行的测试都运行在同一个JVM下,这个属性值还是会保留被设的值,并且会影响在后面运行的测试用例行为。所以,让我们来改进一下:

Test.java
private String oldValue;
@Before
public void setup() {
  // setProperty returns the old value of that property.
  oldValue = System.setProperty("prop", "true");
}
 
@After
public void teardown() {
  System.setProperty("prop", oldValue);
}
@Test
// use that prop..

看起来挺不错吧,不是吗?但还是存在一个问题:如果属性值没有设置,在setup中的setProperty返回null,而teardown会抛出一个NullPointerException。所以正确的使系统属性维持不变的单元测试应该是:

Test.java
private String oldValue;
@Before
public void setup() {
  // setProperty returns the old value of that property.
  oldValue = System.setProperty("prop", "true");
}
 
@After
public void teardown() {
  if( oldValue == null ) {
    System.clearProperty("prop");
  } else {
    System.setProperty("prop", oldValue);
  }
}
@Test
// Use that property..

这会有点啰嗦..基于Java 7的try-with-resources 语句的帮助类实现了同样的功能:

Test.java
private ScopedProperty property;
@Before
public void setup() {
  property = new ScopedProperty("prop", "true");
}
@After
public void teardown() {
  // Does same things like above.
  property.close();
}
@Test
// use that prop..

or also:

或者:

Test.java
@Test
public void test() {
  try(ScopedProperty prop = new ScopedProperty("prop", "true")) {
    // use that prop..
  }
}

当然,最好还是直接实现Junit规则,在这种情况下你不需要写@After。例如:

Test.java
@ClassRule
public static ScopedPropertyRule prop = new ScopedPropertyRule("prop", "true");
@Test
public void test1() {
 // use that prop..
}
@Test
public void test2() {
// use that prop..}

属性在@BeforeClass中进行设置,但会在@AfterClass中恢复成原始的状态。

结论:无副作用的测试可以提升自动化测试质量

要避免由于执行顺序引起的测试错误,单元测试必须做到没有副作用。现实世界的例子中证明这个话题的关联性。更深入这个练习,我们开发了一些用于系统属性的帮助类。相同的原理同样适用于其他环境对象,例如线程属性,Java安全管理器和类似ScopedProperty和ScopedPropertyRule之类的类,这些都可以很容易的实现。当我们在不断努力提升自动测试测试质量时,我对其他最佳实践保持兴趣。给我们留言,说一下你在开发团队里面的工作。(感谢Reinhold Füreder对Junit规则的引入!)

附录

ScopedProperty.java
/**
  * A helper to switch a system property value and restore the previous one.
  *
  * When used in try-with-resources, restores the values automatically.
  */
public class ScopedProperty implements AutoCloseable {
 
    private final String key;
    private final String oldValue;
 
  /**
     *
     * @param key The System.setProperty key
     * @param value The System.setProperty value to switch to.
     */
    public ScopedProperty(final String key, final String value) {
        this.key = key;
        oldValue = System.setProperty(key, value);
    }
 
    @Override
    public void close() {
        // Can't use setProperty(key, null) -> Throws NullPointerException.
        if( oldValue == null ) {
            // Previously there was no entry.
            System.clearProperty(key);
        } else {
            System.setProperty(key, oldValue);
        }
    }
}

这里的ScopedProperty实现了AutoCloseable来完成try-with-ressouces语句

ScopedPropertyRule.java
/**
 * A JUnit test rule, which changes a property value within a test
 * and restores the original one afterwards.
 * See {@link ScopedProperty}.
 */
public class ScopedPropertyRule extends ExternalResource {
 
    private final String key;
    private final String value;
    private ScopedProperty scopedProperty;
 
    public ScopedPropertyRule(final String key, final String value) {
        this.key =key;
        this.value = value;
    }
 
    @Override
    protected void before() throws Throwable {
        scopedProperty = new ScopedProperty(key, value);
    }
 
    @Override
    protected void after() {
        scopedProperty.close();
    }
}

这个类使用了ExternalResource来实现Junit测试规则。

英文原文:compuware 翻译:ImportNew.com - 陈 晓舜
来自: ImportNew
3
0
评论 共 3 条 请登录后发表评论
3 楼 wisely2012 2014-04-15 23:33
zjumty 写道
很多开发人员都不愿意写Junit, 因为写Junit并不比写实现简单.

其实我觉得还是大家都觉得没有必要,都对自己太有信心了
2 楼 wisely2012 2014-04-15 23:32
      
1 楼 zjumty 2014-03-24 13:52
很多开发人员都不愿意写Junit, 因为写Junit并不比写实现简单.

发表评论

您还没有登录,请您登录后再发表评论

相关推荐

  • 单元测试的最佳实践

    本文将介绍基于集成Mockito + PowerMock + H2 + EmbededRedis 的单元测试实践方案,整套单元测试环境将完全脱离Spring框架进行,使得功能验证更加纯粹简单。

  • 腾讯质量效能提升最佳实践:智能自动化测试探索和建设

    相比于人工测试,自动化测试有更高的测试覆盖率,在面对TOP300机型与TOP500机型时的测试效率也更高。同时,自动化测试能配合持续集成的工具,做到快速响应的版本,达到一个版本就可以进行一次回归测试,甚至一个代码...

  • 测试老鸟常用的自动化测试工具有哪些?

    自动化测试正在逐步取代部分手动测试,因为它可以节省时间并提高测试质量。特别是在进行回归测试的情况下,自动化可以通过多种方式提高效率。手动进行重复测试是浪费时间和资源。此外,由于重复测试可能会遗漏,因此...

  • 04.为什么要做自动化测试?什么样的项目适合做自动化测试?

    文章目录什么是自动化测试?为什么需要自动化测试?...不管你是刚入行的小白,还是已经在做软件测试的工作,相信你一定听说过或者接触过自动化测试。那么,自动化测试到底是什么意思呢? 顾名思义,自动

  • React 单元测试实践

    React单元测试实践 单元测试(unit testing),是指对软件中的最小可测试单元进行检查和验证。...你写了一个函数,log一下或者在界面上点一下,这,也是单元测试,把这种单元测试称为临时单元测试。临时单元测试.

  • 【测试开发】自动化测试在美团外卖的实践与落地

    随着美团到家业务的发展,系统复杂度也在持续增长。...因此,引入自动化测试就显得十分有必要,本文介绍了美团外卖在自动化测试方向做的一些探索和实践,希望对从事相关领域工作的同学能够带来一些启发或帮助。

  • 接口自动化测试实践指导(上):接口自动化需要做哪些准备工作

    在这里呢,首先抛个问题:接口测试和接口自动化有什么不同和区别?在这里先不做解答,大家可以带着这个问题去阅读后面的内容,相信自然会得到这个问题的答案。1.2 做接口自动化的原因如果要一一列举原因会列举出很多...

  • 途游游戏 DevOps 实践|都说「单元测试」好,「AAAC四步法」少不了

    立即获取途游单元测试最佳实践四步法

  • 功能测试和自动化测试的优缺点

    简单来说,自动化测试就是程序测试程序,是程序就会有缺陷,所以不能保证测试工程师开发的脚本就一定没有缺陷,如果代码有 一个小小的逻辑错误,哪怕是一个条件判断的误写也会导致测试结果完全出错,当然对于自动化...

  • 单元测试 - C/C++

    什么是单元测试 单元测试是软件开发过程中的一种质量保证手段。最初的来源是想模仿对硬件芯片做单元测试那样,在软件中也能对小的软件...传统的单元测试由于缺乏自动化工具的支持,往往在测试中通过打印输出测试结果

  • 测试开发是什么?为什么现在那么多公司都要招聘测试开发?

    测试开发是什么?为什么现在那么多公司都要招聘测试开发?

  • JAVA面试题分享三百一十四:单元测试在货拉拉的落地与实践

    而且尽管众多技术书籍都有单元测试章节,但最佳实践仍然不容易。原因在于单元测试虽然入门简单,但真正的工程实践却颇具挑战。 2.1 误区 全覆盖误区: 认为所有代码都需单元测试,实际上,并非所有代码都需单元测试...

  • 通用AI元素识别在UI自动化测试的最佳实践

    前言在UI自动化测试中,元素被识别出来后,才能更加精确地模拟相关用户行为,才能更好地开展自动化的其他内容。一般移动端APP会有页面元素属性,比如:ID,ClassName,Text等,可...

  • EDAS 3.0 微服务测试最佳实践

    该文章是基于阿里云商业化产品 EDAS 3.0的微服务实践,如果您的团队具备较强的微服务测试能力,那么希望我们在微服务测试方面的实践和背后的思考,可以为您提供一些参考。点击这里,观看直播,了解更多 EDAS 3.0 ...

  • 性能测试:方法、工具与最佳实践

    在当今快节奏的软件开发环境中,性能...通过选择合适的性能测试方法和工具,并遵循最佳实践,开发团队可以更好地发现和解决性能问题,提升应用程序的质量和用户体验。希望本文对你在性能测试方面的工作和学习有所帮助。

  • TDD及单元测试最佳实践

    其基本思想是通过测试来推动整个开发的进行,但测试驱动开发并不只是单纯的测试工作,而是把需求分析、设计、质量控制量化的过程。为什么要采用TDD呢?TDD有如下几点优势:在开发的过程中,把大的功能块拆分成小的...

  • 自动化测试框架

    降低维护成本,测试工作效率提升和提高质量保证团队的投资回报率是优化敏捷流程时所提供的主要优势之一。出于以下原因,自动化对于高效的测试过程至关重要:软件开发领域的管理人员对实现自动化框架如何使他们的业务...

  • 自动化测试实践

    微服务架构 Microservice Architecture二、自动化测试 Test Automation1、测试分类2、微服务架构为测试带来的挑战3、自动化测试4、自动化测试分层三、自动化测试最佳实践1、纺锤型向金字塔型过渡2、测试质量评估四、...

  • JNI入门教程之HelloWorld篇

       本文讲述如何使用JNI技术实现HelloWorld,目的是让读者熟悉JNI的机制并编写第一个HelloWorld程序。     Java Native Interface(JNI)是Java语言的本地编程接口,是J2SDK的一部分。在java程序中,我们可以通过JNI实现一些用java语言不便实现的功能。通常有以下几种情况我们需要使用JNI来实现。标准的java类库没有提供你的应

Global site tag (gtag.js) - Google Analytics