天天看点

《代码整洁之道》—第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( )的调用。在测试某段棘手的代码时,正当如此操作。

下面是个例子:

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

这种手法有许多毛病:

你得手工找到合适的地方来插入方法调用;

你怎么知道在哪里插入调用、插入什么调用?

不必要地在产品环境中留下这类代码,将拖慢代码执行速度;

这是种无的放矢的手段。你可能找不到缺陷。实际上,这不在你把握之中。

我们所需要的,是一种在测试中但不在生产中实现的手段。我们还需要为多次运行轻易地调整配置,从而增加总的发现错误机会。

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

13.9.9 自动化

可以使用aspect-oriented framework、cglib或asm之类工具通过编程来装置代码。例如,可以使用有单个方法的类:

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

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

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

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

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

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

继续阅读