单元测试怎么测试线程_单元测试线程代码的5个技巧
單元測試怎么測試線程
以下是一些技巧,說明如何進(jìn)行代碼的邏輯正確性測試(與多線程正確性相對)。我發(fā)現(xiàn)本質(zhì)上有兩種帶有線程代碼的刻板印象模式:
測試這兩種類型的代碼可能很難。 該工作是在另一個線程中完成的,因此完成的通知可能是不透明的,或者隱藏在抽象級別的后面。
該代碼在GitHub上 。
提示1 –生命周期管理對象
具有受管生命周期的對象更易于測試,該生命周期可進(jìn)行設(shè)置和拆卸,這意味著您可以在測試后進(jìn)行清理,而沒有亂碼干擾任何其他測試。
public class Foo {private ExecutorService executorService;public void start() {executorService = Executors.newSingleThreadExecutor();}public void stop() {executorService.shutdown();} }技巧2 –設(shè)置測試超時
代碼中的錯誤(如下所示)可能導(dǎo)致多線程測試永遠(yuǎn)不會完成,例如(例如)您正在等待從未設(shè)置的標(biāo)志。 JUnit允許您在測試中設(shè)置超時。
... @Test(timeout = 100) // in case we never get a notification public void testGivenNewFooWhenIncrThenGetOne() throws Exception { ...技巧3 –在與測試相同的線程中運行任務(wù)
通常,您將擁有一個在線程池中運行任務(wù)的對象。 這意味著您的單元測試可能必須等待任務(wù)完成,但是您不知道什么時候完成。 您可能會猜測,例如:
public class Foo {private final AtomicLong foo = new AtomicLong(); ...public void incr() {executorService.submit(new Runnable() {@Overridepublic void run() {foo.incrementAndGet();}});} ...public long get() {return foo.get();} }public class FooTest {private Foo sut; // system under test@Beforepublic void setUp() throws Exception {sut = new Foo();sut.start();}@Afterpublic void tearDown() throws Exception {sut.stop();}@Testpublic void testGivenFooWhenIncrementGetOne() throws Exception {sut.incr();Thread.sleep(1000); // yuk - a slow test - don't do thisassertEquals("foo", 1, sut.get());} }但這是有問題的。 執(zhí)行是不統(tǒng)一的,因此不能保證它可以在另一臺機器上運行。 它非常脆弱,對代碼的更改可能會導(dǎo)致測試失敗,因為它突然花費了太長時間。 它的速度很慢,因為當(dāng)它失敗時您會大方入睡。
一個訣竅是使任務(wù)同步運行,即與測試在同一線程中運行。 這可以通過注入執(zhí)行程序來實現(xiàn):
public class Foo { ...public Foo(ExecutorService executorService) {this.executorService = executorService;} ...public void stop() {// nop }然后,您可以使用同步執(zhí)行程序服務(wù)(概念類似于SynchronousQueue)進(jìn)行測試:
public class SynchronousExecutorService extends AbstractExecutorService {private boolean shutdown;@Overridepublic void shutdown() {shutdown = true;}@Overridepublic List<Runnable> shutdownNow() {shutdown = true; return Collections.emptyList();}@Overridepublic boolean isShutdown() {shutdown = true; return shutdown;}@Overridepublic boolean isTerminated() {return shutdown;}@Overridepublic boolean awaitTermination(final long timeout, final TimeUnit unit) {return true;}@Overridepublic void execute(final Runnable command) {command.run();} }不需要睡覺的更新測試:
public class FooTest {private Foo sut; // system under testprivate ExecutorService executorService;@Beforepublic void setUp() throws Exception {executorService = new SynchronousExecutorService();sut = new Foo(executorService);sut.start();}@Afterpublic void tearDown() throws Exception {sut.stop();executorService.shutdown();}@Testpublic void testGivenFooWhenIncrementGetOne() throws Exception {sut.incr();assertEquals("foo", 1, sut.get());} } 注意,您需要從外部對Foo的執(zhí)行程序進(jìn)行生命周期管理。
技巧4 –從線程中提取工作
如果您的線程正在等待一個事件,或者等待它完成任何工作之前的某個時間,請將該工作提取到其自己的方法中并直接調(diào)用它。 考慮一下:
public class FooThread extends Thread {private final Object ready = new Object();private volatile boolean cancelled;private final AtomicLong foo = new AtomicLong();@Overridepublic void run() {try {synchronized (ready) {while (!cancelled) {ready.wait();foo.incrementAndGet();}}} catch (InterruptedException e) {e.printStackTrace(); // bad practise generally, but good enough for this example}}public void incr() {synchronized (ready) {ready.notifyAll();}}public long get() {return foo.get();}public void cancel() throws InterruptedException {cancelled = true;synchronized (ready) {ready.notifyAll();}} }而這個測試:
public class FooThreadTest {private FooThread sut;@Beforepublic void setUp() throws Exception {sut = new FooThread();sut.start();Thread.sleep(1000); // yukassertEquals("thread state", Thread.State.WAITING, sut.getState());}@Afterpublic void tearDown() throws Exception {sut.cancel();}@Afterpublic void tearDown() throws Exception {sut.cancel();}@Testpublic void testGivenNewFooWhenIncrThenGetOne() throws Exception {sut.incr();Thread.sleep(1000); // yukassertEquals("foo", 1, sut.get());} }現(xiàn)在提取工作:
@Overridepublic void run() {try {synchronized (ready) {while (!cancelled) {ready.wait();undertakeWork();}}} catch (InterruptedException e) {e.printStackTrace(); // bad practise generally, but good enough for this example}}void undertakeWork() {foo.incrementAndGet();}重構(gòu)測試:
public class FooThreadTest {private FooThread sut;@Beforepublic void setUp() throws Exception {sut = new FooThread();}@Testpublic void testGivenNewFooWhenIncrThenGetOne() throws Exception {sut.incr();sut.undertakeWork();assertEquals("foo", 1, sut.get());} }提示5 –通過事件通知狀態(tài)更改
前面兩個技巧的替代方法是使用通知系統(tǒng),以便您的測試可以偵聽線程對象。
這是一個面向任務(wù)的示例:
public class ObservableFoo extends Observable {private final AtomicLong foo = new AtomicLong();private ExecutorService executorService;public void start() {executorService = Executors.newSingleThreadExecutor();}public void stop() {executorService.shutdown();}public void incr() {executorService.submit(new Runnable() {@Overridepublic void run() {foo.incrementAndGet();setChanged();notifyObservers(); // lazy use of observable}});}public long get() {return foo.get();} }及其對應(yīng)的測試(注意使用超時):
public class ObservableFooTest implements Observer {private ObservableFoo sut;private CountDownLatch updateLatch; // used to react to event@Beforepublic void setUp() throws Exception {updateLatch = new CountDownLatch(1);sut = new ObservableFoo();sut.addObserver(this);sut.start();}@Overridepublic void update(final Observable o, final Object arg) {assert o == sut;updateLatch.countDown();}@Afterpublic void tearDown() throws Exception {sut.deleteObserver(this);sut.stop();}@Test(timeout = 100) // in case we never get a notificationpublic void testGivenNewFooWhenIncrThenGetOne() throws Exception {sut.incr();updateLatch.await();assertEquals("foo", 1, sut.get());} }這有優(yōu)點和缺點:
優(yōu)點:
缺點:
參考:來自Alex Collins博客博客的JCG合作伙伴 Alex Collins提供的5個單元測試線程代碼的技巧 。
翻譯自: https://www.javacodegeeks.com/2012/09/5-tips-for-unit-testing-threaded-code.html
單元測試怎么測試線程
總結(jié)
以上是生活随笔為你收集整理的单元测试怎么测试线程_单元测试线程代码的5个技巧的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 消息称苹果 iPhone 15 Pro
- 下一篇: 谷歌相机 App 外观升级,自 Pixe