`
cxshun
  • 浏览: 724527 次
  • 性别: Icon_minigender_1
  • 来自: 广州
社区版块
存档分类
最新评论

你的单元测试有多稳定?提升自动测试质量的最佳实践

 
阅读更多

本文转载自http://www.importnew.com/10312.html,该文由本人翻译,现只是在自己博客发布,请转载时注明一下转自http://www.importnew.com/10312.html。

 

我们超过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..

或者:

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测试规则

0
0
分享到:
评论

相关推荐

    单元测试最佳实践(JAVA)

    面向对象编程是软件开发中的一项核心技能,特别是在Java这样的面向对象语言中,理解面向对象的基本概念对于进行...通过持续实践和学习,开发者可以提高编写高质量单元测试的能力,从而提升整个软件项目的质量和稳定性。

    自动化测试最佳实践示例视频

    "自动化测试最佳实践示例视频"这个标题表明我们即将探讨的是如何有效地运用自动化测试技术,并通过视频实例来展示这些最佳实践。下面我们将深入讲解自动化测试的核心概念、常用工具以及最佳实践策略。 自动化测试的...

    软件测试---性能测试最佳实践

    ### 软件测试——性能测试最佳实践 #### 一、引言 随着信息技术的快速发展,用户对软件质量的...本文从理论到实践详细介绍了性能测试的核心内容及最佳实践方法,希望能为相关领域的专业人士提供有价值的参考和指导。

    手机软件测试最佳实践.zip

    本文将深入探讨手机软件测试的最佳实践,帮助开发者和测试工程师提升测试效率,降低软件缺陷,提高用户体验。 首先,理解测试目标至关重要。手机软件测试的目标不仅包括找出并修复程序错误,还要评估应用程序的性能...

    单元测试 Vector Cast Train资料

    在软件开发领域,尤其是汽车电子软件的开发过程...通过深入学习和实践Vector Cast Train资料,汽车电子软件的开发者可以提升其单元测试能力,确保软件的质量和可靠性,进而满足汽车行业对软件的高安全性和稳定性要求。

    手机测试最佳实践

    本文将深入探讨手机测试的基础知识,最佳实践,以及如何通过有效测试策略提升应用的质量。 1. 测试目标与范围 手机测试的目标在于发现并修复应用中的缺陷,确保兼容性、性能、安全性及用户体验。测试范围包括功能...

    单元测试 单元测试 java

    10. **测试最佳实践** - 测试应独立、可重复且快速执行。 - 测试应尽可能覆盖所有代码路径,包括边界条件和异常处理。 - 避免硬编码测试依赖,使用配置文件或依赖注入。 通过深入理解和熟练应用这些概念,开发者...

    单元测试相关文档两则_wwt

    2. **最佳实践**:如何编写好的单元测试,比如保持测试用例简洁、避免测试实现细节,以及编写可读性强的测试名称。 3. **测试的隔离性**:如何确保一个单元测试只测试一个特定的功能,不涉及其他模块,以确保测试...

    Python自动化运维--技术与最佳实践 [刘天斯著].zip

    6. **自动化测试**:Python的unittest和pytest框架可以帮助编写自动化测试脚本,确保服务的稳定性和质量。 7. **故障排查和异常处理**:Python的异常处理机制(try-except-finally)和调试工具pdb是解决运维问题的...

    手机软件测试最佳实践

    根据提供的文件信息,本文将围绕“手机软件测试最佳实践”这一主题进行深入探讨,并结合描述中的关键词“国内最详细的手机软件测试书籍”、“案例清晰”和“简单易懂”,以及标签“全章节手机软件测试”来展开具体的...

    有关单元测试的培训资料

    通过本“单元测试培训资料”,你将获得关于如何有效地实施单元测试,提高代码质量和项目稳定性的全面理解。无论是初级开发者还是经验丰富的工程师,掌握单元测试都将对你的软件开发生涯产生积极影响。

    Java测试与设计.从单元测试到自动Web测试

    《Java测试与设计:从单元测试到自动Web测试》是一本深入探讨Java应用程序测试和设计的书籍,旨在帮助开发者和测试工程师提升软件质量,确保代码的健壮性和可靠性。本书全面覆盖了单元测试、集成测试以及自动化Web...

    Python技术单元测试实践.docx

    2. **提高代码质量**:通过持续的测试和反馈循环,单元测试有助于提升代码的整体质量,减少潜在错误的发生。 3. **文档作用**:良好的单元测试用例本身就是一种很好的文档,它们能够清晰地表达出各个模块的功能和...

    selenium2 python 自动化测试实践pdf

    这本书适合对Python有一定基础,希望提升自动化测试技能的开发人员,以及从事Web应用测试工作的专业人士。通过实际操作和练习,读者将能够运用Selenium2和Python构建高效、可靠的自动化测试框架,为Web应用的质量...

    智能设备项目的单元测试(VIDEO)

    【智能设备项目的单元测试(VIDEO)】:这个...通过观看"livemeeting.wmv"这个视频,观众可以深入了解智能设备项目的单元测试实践,学习如何有效地为自己的项目构建稳定且可靠的测试套件,从而提升软件质量和开发效率。

    Java测试与与设计——从单元测试到Web测试

    #### 最佳实践 - **覆盖关键路径**:确保所有重要的业务逻辑路径都被测试覆盖。 - **独立性**:每个测试用例应独立于其他用例,避免相互依赖。 - **自动化**:将单元测试集成到持续集成流程中,实现自动化测试,...

    自动化测试案例自动化测试案例

    因此,最佳实践包括选择合适的技术栈、建立良好的文档、持续培训团队、并适时评估自动化测试的投资回报率。 综上所述,自动化测试案例是软件质量保障的关键环节。通过精心设计和有效实施,可以极大地提升测试效率,...

    开源单元测试库CppTest源码及测试程序

    单元测试是一种软件开发的最佳实践,它鼓励程序员对每个函数、类或模块编写独立的测试用例,以便在修改代码或添加新功能时确保现有代码的行为不受影响。CppTest库提供了丰富的断言机制,使得测试编写简单而直观,...

    iOS单元测试实例

    在这个实例中,我们将探讨`iOS_Calc`和`Calculator-iOS`两个项目,它们可能是模拟一个简单计算器的应用,用于演示单元测试的基本概念和最佳实践。 `iOS UnitTest`标签明确指出这个实例关注的是iOS平台上的单元测试...

Global site tag (gtag.js) - Google Analytics