From Gossip@caterpillar

JUnit Gossip: LoadTest

有很多應用程式,都是在多人的環境之下使用,例如網站應用程式,您可以使用LoadTest來模擬多使用者的情況下,應用程式的行為,例如您撰寫了一個Resource類別:
  • Resource.java
package onlyfun.caterpillar;

public class Resource {
public Resource() {
// 建立這個資源很耗時
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}

public void doSomething() {
try {
// I'm doing something...
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

假設您有個ResourceClient會使用這個Resource:
  • ResourceClient.java
package onlyfun.caterpillar;

public class ResourceClient {
public ResourceClient() {
}

public synchronized boolean doSomething() {
Resource resource = new Resource();
resource.doSomething();
return true;
}
}

這只是一個簡單的程式,傳回true表示doSomething()正確執行無誤,您也許會為它撰寫一個TestCase:
  • ResourceClientTestCase.java
package onlyfun.caterpillar.test;

import onlyfun.caterpillar.ResourceClient;
import junit.framework.TestCase;

public class ResourceClientTestCase extends TestCase {
private ResourceClient client = new ResourceClient();;

public ResourceClientTestCase(String name) {
super(name);
}

public void testResourceClient() {
assertTrue(client.doSomething());
}
}

在單元測試正確無誤之後,接下來您打算測試多人環境下會有什麼結果,您可以使用LoadTest,例如:
int users = 10;
Test testCase = new ResourceClientTestCase("testResourceClient");
Test loadTest = new LoadTest(testCase, users);

這個程式片段模擬10個使用者同時對ResourceClientTest進行測試,也許您會搭配TimedTest來測試一下是否可以在預期時間下完成任務,例如:

  • ResourceClientLoadTest.java
package onlyfun.caterpillar.test;

import com.clarkware.junitperf.LoadTest;
import com.clarkware.junitperf.TimedTest;

import junit.framework.Test;

public class ResourceClientLoadTest {
public static Test suite() {
int users = 10;
int maxElapsedTime = 10000;
Test testCase = new ResourceClientTestCase("testResourceClient");
Test loadTest = new LoadTest(testCase, users);
Test timedTest = new TimedTest(loadTest, maxElapsedTime);
return timedTest;
}

public static void main(String[] args) {
junit.textui.TestRunner.run(suite());
}
}

執行測試的結果是失敗的:
..........TimedTest (WAITING): LoadTest (NON-ATOMIC): ThreadedTest: testResourceClient(onlyfun.caterpillar.test.ResourceClientTestCase): 25000 ms
F
Time: 25
There was 1 failure:
1) LoadTest (NON-ATOMIC): ThreadedTest: testResourceClient(onlyfun.caterpillar.test.ResourceClientTestCase)junit.framework.AssertionFailedError: Maximum elapsed time exceeded! Expected 10000ms, but was 25000ms.
    at com.clarkware.junitperf.TimedTest.runUntilTestCompletion(TimedTest.java:161)
    at com.clarkware.junitperf.TimedTest.run(TimedTest.java:138)
    at onlyfun.caterpillar.test.ResourceClientLoadTest.main(ResourceClientLoadTest.java:19)

FAILURES!!!
Tests run: 10,  Failures: 1,  Errors: 0

建立Resource是個耗時的工作,觀察Resource的行為,它其實是可以重用的物件,所以您考慮製作一個簡單的Pool:
  • ResourcePool.java
package onlyfun.caterpillar;

import java.util.*;

public class ResourcePool {
private int max;
private List resources;

public ResourcePool(int max) {
this.max = max;
resources = new LinkedList();
}

public synchronized Resource getResource() {
if(resources.size() == 0) {
return new Resource();
}
else {
int lastIndex = resources.size() - 1;
return (Resource) resources.remove(lastIndex);
}
}

public synchronized void putResource(Resource resource) {
if(resources.size() != max) {
resources.add(resource);
}
}
}

接著修改一下ResourceClient:
  • ResourceClient.java
package onlyfun.caterpillar;

public class ResourceClient {
private ResourcePool pool;

public ResourceClient() {
pool = new ResourcePool(10);
}

public synchronized boolean doSomething() {
Resource resource = pool.getResource();
resource.doSomething();
pool.putResource(resource);

return true;
}
}

再次運行測試,由於這次可以重用Resource物件,不必每次都耗時建立物件,所以測試通過:
..........TimedTest (WAITING): LoadTest (NON-ATOMIC): ThreadedTest: testResourceClient(onlyfun.caterpillar.test.ResourceClientTestCase): 7031 ms

Time: 7.031

OK (10 tests)

您還可以搭配Timer實例來模擬使用者的造訪頻率,ConstantTimer可以設定使用者造訪的時間固定間隔,例如:
int users = 10;
Timer timer = new ConstantTimer(1000); // 每個使用者每隔1000毫秒依次造訪
Test testCase = new ResourceClientTestCase("testResourceClient");
Test loadTest = new LoadTest(testCase, users, timer);

RandomTimer則可以模擬使用者隨機間隔時間造訪的情況。

更多有關 JUnitPerf 的資料,可以參考其官方網站之 文件