《代码整洁之道》—第13章13.9节测试线程代码

简介:

本节书摘来自异步社区《代码整洁之道》一书中的第13章13.9节测试线程代码,作者【美】Robert C. Martin,更多章节内容可以访问云栖社区“异步社区”公众号查看。

13.9 测试线程代码
证明代码的正确性不切实际。测试并不能确保正确性。然而,好的测试却能尽量降低风险。这对于所有单线程解决方案都是对的。当有两个或多个线程使用同一代码段和共享数据,事情就变得非常复杂了。

建议:编写有潜力曝露问题的测试,在不同的编程配置、系统配置和负载条件下频繁运行。如果测试失败,跟踪错误。别因为后来测试通过了后来的运行就忽略失败。

有一大堆问题要考虑。下面是一些精练的建议:

将伪失败看作可能的线程问题;
先使非线程代码可工作;
编写可插拔的线程代码;
编写可调整的线程代码;
运行多于处理器数量的线程;
在不同平台上运行;
调整代码并强迫错误发生。

13.9.1 将伪失败看作可能的线程问题
线程代码导致“不可能失败的”失败。多数开发者缺乏有关线程如何与其他代码(可能由其他作者编写)互动的直觉。线程代码中的缺陷可能在一千或一百万次执行中才会显现一次。重复执行想要复现问题令人沮丧。所以开发者常常会将失败归咎于宇宙射线、硬件错误或其他“偶发事件”。最好假设这种偶发事件根本不存在。“偶发事件”被忽略得越久,代码就越有可能搭建于不完善的基础之上。

建议:不要将系统错误归咎于偶发事件。

13.9.2 先使非线程代码可工作
这看起来太浅显,但强调一下不无益处。确保线程之外的代码可工作。通常,这意味着创建由线程调用的POJO。POJO与线程无涉,所以可在线程环境之外测试。能放进POJO中的代码越多越好。

建议:不要同时追踪非线程缺陷和线程缺陷。确保代码在线程之外可工作。

13.9.3 编写可插拔的线程代码
编写可在数个配置环境下运行的线程代码:

单线程与多个线程在执行时不同的情况;
线程代码与实物或测试替身互动;
用运行快速、缓慢和有变动的测试替身执行;
将测试配置为能运行一定数量的迭代。
建议:编写可插拔的线程代码,这样就能在不同的配置环境下运行。

13.9.4 编写可调整的线程代码
要获得良好的线程平衡,常常需要试错。一开始,在不同的配置环境下监测系统性能。要允许线程数量可调整。在系统运行时允许线程发生变动。允许线程依据吞吐量和系统使用率自我调整。

13.9.5 运行多于处理器数量的线程
系统在切换任务时会发生一些事。为了促使任务交换的发生,运行多于处理器或处理器核心数量的线程。任务交换越频繁,越有可能找到错过临界区或导致死锁的代码。

13.9.6 在不同平台上运行
2007年,我们做了一套关于并发编程的课程。该课程主要在OS X下开发,在运行于虚拟机的Windows XP上展示。用于演示的测试失败条件,在OS X上要比在XP上失败得更频繁。

被测试的代码已知是不正确的。这正强调了不同操作系统有着不同线程策略的事实,不同的线程策略影响了代码的执行。在不同环境中,多线程代码的行为也不一样[16]。应该在所有可能部署的环境中运行测试。

建议:尽早并经常地在所有目标平台上运行线程代码。

13.9.7 装置试错代码
并发代码中藏有缺陷,这并不罕见。简单的测试往往无法曝露这些缺陷。实际上,缺陷经常隐藏于一般处理过程中。可能好几个小时、好几天甚至好几个星期才会跳出来一次!

线程中的缺陷之所以如此不频繁、偶发、难以重现,是因为在几千个穿过脆弱区域的可能路径当中,只有少数路径会真的导致失败。经过会导致失败的路径的可能性惊人地低。所以,侦测与调试也非常之难。

怎么才能增加捕捉住如此罕见之物的机会?可以装置代码,增加对Object.wait( )、Object.sleep( )、Object.yield( )和Object.priority( )等方法的调用,改变代码执行顺序。

这些方法都会影响执行顺序,从而增加了侦测到缺陷的可能性。有问题的代码,最好尽早、尽可能多地通不过测试。

有两种装置代码的方法:

硬编码;
自动化。
13.9.8 硬编码
你可以手工向代码中插入wait( )、sleep( )、yield( )和priority( )的调用。在测试某段棘手的代码时,正当如此操作。

下面是个例子:

public synchronized String nextUrlOrNull() {
  if(hasNext()) {
    String url = urlGenerator.next();
    Thread.yield(); // inserted for testing.
    updateHasNext();
    return url;
  } 
  return null;
}

插入对yield( )的调用,将改变代码的执行路径,由此而可能导致代码在以前未失败过的地方失败。如果代码的确出错,那并非是因为你插入了yield( )方法调用[17]。代码出错了,这便是失败的原因。

这种手法有许多毛病:

你得手工找到合适的地方来插入方法调用;
你怎么知道在哪里插入调用、插入什么调用?
不必要地在产品环境中留下这类代码,将拖慢代码执行速度;
这是种无的放矢的手段。你可能找不到缺陷。实际上,这不在你把握之中。
我们所需要的,是一种在测试中但不在生产中实现的手段。我们还需要为多次运行轻易地调整配置,从而增加总的发现错误机会。

无疑,如果将系统分解为对线程及控制线程的类一无所知的POJO,就能更容易地找到装置代码的位置。而且,还能创建许多个以不同方式调用sleep、yield等方法的POJO测试。

13.9.9 自动化
可以使用Aspect-Oriented Framework、CGLIB或ASM之类工具通过编程来装置代码。例如,可以使用有单个方法的类:

public class ThreadJigglePoint {
  public static void jiggle() {
  }
}

可以在代码的不同位置调用这个方法:

public synchronized String nextUrlOrNull() {
  if(hasNext()) {
       ThreadJiglePoint.jiggle();
       String url = urlGenerator.next();
       ThreadJiglePoint.jiggle();
       updateHasNext();
       ThreadJiglePoint.jiggle();
       return url;
  } 
  return null;
}

如此,你就得到了一个随机选择无所作为、睡眠或让步的方面。

或者,想象ThreadJigglePoint类有两种实现。第一种实现jiggle什么都不做,在生产环境中使用。第二种实现生成一个随机数,在睡眠、让步或径直执行间做选择。如果上千次地做这种随机测试,大概就能找到一些缺陷的根源。假如测试都通过了,至少你可以说自己已谨慎对待。这种方法看似有点过于简单,但确是替代复杂工具的一种可选方案。

有一种叫做ConTest[18]的工具,由IBM开发,能做类似的事情,但做法却稍微复杂些。

要点是让代码“异动”,从而使线程以不同次序执行。编写良好的测试与“异动”相组合,能有效地增加发现错误的机会。

建议:使用异动策略搜出错误。

相关文章
|
2月前
|
Java 关系型数据库 数据库连接
Mybatis+MySQL动态分页查询数据经典案例(含代码以及测试)
Mybatis+MySQL动态分页查询数据经典案例(含代码以及测试)
34 1
|
2月前
多线程案例-定时器(附完整代码)
多线程案例-定时器(附完整代码)
242 0
|
3月前
|
测试技术
包含用例执行时间的测试报告代码
包含用例执行时间的测试报告代码
|
4月前
|
数据采集 Python
【Python自动化】多线程BFS站点结构爬虫代码,支持中断恢复,带注释
【Python自动化】多线程BFS站点结构爬虫代码,支持中断恢复,带注释
28 0
|
4月前
|
Java
Java使用线程池代码
Java使用线程池代码
35 0
|
4月前
|
Java
|
10天前
|
jenkins Devops 测试技术
单元测试与质量保证:确保Visual Basic代码的健壮性
【4月更文挑战第27天】在VB开发中,单元测试是保证代码质量和软件健壮性的关键。本文介绍了单元测试的基础,包括其定义和好处,如提高代码质量、促进重构。接着,讨论了MSTest、NUnit和xUnit等VB单元测试工具。遵循TDD原则和最佳实践,编写独立、有针对性的测试,并注重测试速度和覆盖率。通过示例展示了如何在Visual Studio中设置和运行测试。最后,提到了持续集成和自动化测试工具,如Jenkins和静态代码分析工具,以提升软件开发效率和质量。单元测试不仅是技术手段,更是提升团队协作和软件工程水平的文化体现。
|
3天前
|
测试技术
使用CLion创建Cmake项目,使用GoogleTest和GoogleMock对代码进行测试
使用CLion创建Cmake项目,使用GoogleTest和GoogleMock对代码进行测试
15 3
|
8天前
|
SQL DataWorks Java
DataWorks操作报错合集之在阿里云 DataWorks 中,代码在开发测试阶段能够成功运行,但在提交后失败并报错“不支持https”如何解决
DataWorks是阿里云提供的一站式大数据开发与治理平台,支持数据集成、数据开发、数据服务、数据质量管理、数据安全管理等全流程数据处理。在使用DataWorks过程中,可能会遇到各种操作报错。以下是一些常见的报错情况及其可能的原因和解决方法。
23 1
DataWorks操作报错合集之在阿里云 DataWorks 中,代码在开发测试阶段能够成功运行,但在提交后失败并报错“不支持https”如何解决
|
28天前
|
算法 安全 Java
java代码 实现AES_CMAC 算法测试
该代码实现了一个AES-CMAC算法的简单测试,使用Bouncy Castle作为安全提供者。静态变量K定义了固定密钥。`Aes_Cmac`函数接受密钥和消息,返回AES-CMAC生成的MAC值。在`main`方法中,程序对给定的消息进行AES-CMAC加密,然后模拟接收ECU的加密结果并进行比较。如果两者匹配,输出"验证成功",否则输出"验证失败"。辅助方法包括将字节转为16进制字符串和将16进制字符串转为字节。

热门文章

最新文章