本文转载自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测试规则
相关推荐
iOS单元测试最佳实践 ,免分,是个课件类型的pdf,感兴趣的下载下来看看
移动app测试实践 顶级互联网企业软件测试和质量提升最佳实践 全本
单元测试最佳实践,对初学者有一定的帮助 。大家可以下载来看看哦
2020QECon全球软件质量&效能大会,AI与测试自动化专场方李志做的UI自动化测试稳定与效率提升之美的报告的PPT文档,分享给大家!
测试环境是研发/测试同学最常用的功能,稳定性直接影响到研发效率, 那如何提升测试环境的稳定性?阿里巴巴应用与基础运维平台高级开发...张劲,通过阿里内部实践,总结了一套测试环境稳定性提升方法,供大家参 考。
无论您是否已决定转向自动化测试,或者您仍在考虑进行自动化测试,了解实现转换的最佳实践以及哪种策略最适合您的组织非常重要。任何复杂性的每个应用程序都可能有自己的测试要求组合,没有两个开发团队完全相同。 ...
自动化测试最佳实践:来自全球经典的自动化案例解析
一般是指软件测试的自动化,软件测试就是在预设条件下运行系统或应用程序,评估运行结果,预先条件应包括正常条件和异常条件。
RTT操作系统基础版,主要描述RTT的创建和起源,以及发展的前景
单元测试的实践:单元测试要做多细?这篇文章主要来源是StackOverflow上的一个回答——“Howdeepareyourunittests?”。一个有13.8K的分的人(JohnNolan)问了个关于TDD的问题,这个问题并不新鲜,最亮的是这个问题的...
软件测试---性能测试最佳实践 软件测试---性能测试最佳实践
如何通过单元测试提高开发效率?软件测试KevlinHnney是英国的一位独立顾问和培训师,其关注的范围主要包括软件架构、模式、开发过程和程序设计语言。在本文中他将谈谈如何通过单元测试提高开发效率。单元测试只会浪费...
自动化测试,单元测试脚本,能让需要了解测试的人,尤其是
互联网单元测试及实践
测试用例实践回顾和简析 研究测试用例的重要意义 软件领域发展对测试用例技术实践的挑战 测试用例制约因素 微软测试用例实践研究 测试用例最佳实践 问题解答
3.1.5. 创建稳定、有意义的测试数据集。 5 3.1.6. 创建专用的测试库。 6 3.1.7. 有效地隔离测试。 7 3.1.8. 分割测试套件。 8 3.1.9. 使用适当的框架(如 DbUnit)简化过程。 9 3.2. DBunit使用最佳实践 9 3.2.1. 每...
本书指导您如何在设计、编程和测试中进行最佳选择。 书中将介绍一个快速有效的方法,教会您利用Java知识构建具有产品价值、可扩展性和高性能的Web应用程序。书中介绍的技术、方法和工具能够使软件开发人员、QA技术员...