您的位置:首页 > 其它

基于Netty的RPC简单框架实现(五):功能测试与性能测试

2015-09-21 20:28 821 查看
1.JUnit依赖

功能测试使用到了JUnit

<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
在pom.xml中添加上方的依赖

2.测试前准备

(1).定义接口JUnitTestInterface

package com.maigo.rpc.test;

import java.util.List;

public interface JUnitTestInterface
{
public String methodWithoutArg();
public String methodWithArgs(String arg1, int arg2);
public JUnitTestCustomObject methodWithCustomObject(JUnitTestCustomObject customObject);
public List<String> methodReturnList(String arg1, String arg2);
public void methodThrowException();
public void methodTimeOut();
public void methodReturnVoid();
public String methodDelayOneSecond();
public int methodForMultiThread(int threadId);
public String methodForPerformance();
}


定义了用于测试的方法

(2).实现接口JUnitTestInterface

package com.maigo.rpc.test;

import java.util.Arrays;
import java.util.List;

public class JUnitTestInterfaceImpl implements JUnitTestInterface
{
public String methodWithoutArg()
{
return "this is return from methodWithoutArg()";
}

public String methodWithArgs(String arg1, int arg2)
{
return arg1 + " = " + arg2;
}

public JUnitTestCustomObject methodWithCustomObject(
JUnitTestCustomObject customObject)
{
JUnitTestCustomObject object = new JUnitTestCustomObject(customObject.getString() + " after",
customObject.getI() + 47);
return object;
}

public List<String> methodReturnList(String arg1, String arg2)
{
return Arrays.asList(arg1, arg2);
}

public void methodThrowException()
{
throw new JUnitTestCustomException();
}

public void methodTimeOut()
{
try
{
Thread.sleep(5000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}

public void methodReturnVoid()
{
return;
}

public String methodDelayOneSecond()
{
try
{
Thread.sleep(1000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}

return "I have sleep 1000ms already.";
}

public int methodForMultiThread(int threadId)
{
return threadId;
}

public String methodForPerformance()
{
return "Maigo";
}
}
实现了用于测试的方法

(3).定义自定义类型JUnitTestCustomObject

package com.maigo.rpc.test;

public class JUnitTestCustomObject
{
private String string;
private int i;

public JUnitTestCustomObject(String string, int i)
{
super();
this.string = string;
this.i = i;
}

public String getString()
{
return string;
}

public void setString(String string)
{
this.string = string;
}

public int getI()
{
return i;
}

public void setI(int i)
{
this.i = i;
}

@Override
public boolean equals(Object obj)
{
JUnitTestCustomObject object = null;
if(obj instanceof JUnitTestCustomObject)
object = (JUnitTestCustomObject)obj;
else
return false;

return (this.string.equals(object.string)) && (this.i == object.i);
}
}


(4).定义自定义异常JUnitTestCustomException

package com.maigo.rpc.test;

public class JUnitTestCustomException extends RuntimeException
{
private static final long serialVersionUID = 591530421634999576L;

public JUnitTestCustomException()
{
super("CustomException");
}
}


3.功能测试

(1).JUnitServerTest


package com.maigo.rpc.test;

import static org.junit.Assert.*;

import org.junit.Test;

import com.maigo.rpc.server.RpcServer;
import com.maigo.rpc.server.RpcServerBuilder;

public class JUnitServerTest
{
@Test
public void testServerStart()
{
JUnitTestInterfaceImpl jUnitTestInterfaceImpl = new JUnitTestInterfaceImpl();
RpcServer rpcServer = RpcServerBuilder.create()
.serviceInterface(JUnitTestInterface.class)
.serviceProvider(jUnitTestInterfaceImpl)
.threads(4)
.bind(3721)
.build();
rpcServer.start();
}

public static void main(String[] args)
{
new JUnitServerTest().testServerStart();
}
}
测试结果:

正常启动RPC服务器(维持服务器开启状态,后续功能测试和性能测试需要服务器在线)

(2).JUnitFunctionTest

package com.maigo.rpc.test;

import static org.junit.Assert.*;

import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

import org.junit.BeforeClass;
import org.junit.Test;

import com.maigo.rpc.aop.RpcInvokeHook;
import com.maigo.rpc.client.RpcClientAsyncProxy;
import com.maigo.rpc.client.RpcClientProxyBuilder;
import com.maigo.rpc.exception.RpcMethodNotFoundException;
import com.maigo.rpc.exception.RpcTimeoutException;
import com.maigo.rpc.future.RpcFuture;
import com.maigo.rpc.future.RpcFutureListener;

public class JUnitFunctionTest
{
/**
* use for sync-mode test
*/
public static JUnitTestInterface jUnitTestInterface;

/**
* use for async-mode test
*/
public static RpcClientAsyncProxy rpcClientAsyncProxy;

private static int integerBeforeHook = 0;
private static int integerAfterHook = 0;
private CountDownLatch countDownLatch;

@BeforeClass
public static void init()
{
RpcInvokeHook hook = new RpcInvokeHook()
{
public void beforeInvoke(String methodName, Object[] args)
{
integerBeforeHook++;
}

public void afterInvoke(String methodName, Object[] args)
{
integerAfterHook++;
}
};

jUnitTestInterface = RpcClientProxyBuilder.create(JUnitTestInterface.class)
.timeout(2000)
.threads(4)
.hook(hook)
.connect("127.0.0.1", 3721)
.build();

rpcClientAsyncProxy = RpcClientProxyBuilder.create(JUnitTestInterface.class)
.timeout(2000)
.threads(4)
.hook(hook)
.connect("127.0.0.1", 3721)
.buildAsyncProxy();
}

@Test
public void testMethodWithoutArg()
{
assertEquals("this is return from methodWithoutArg()",
jUnitTestInterface.methodWithoutArg());
}

@Test
public void testMethodWithArgs()
{
assertEquals("age = 23", jUnitTestInterface.methodWithArgs("age", 23));
assertEquals("born = 1992", jUnitTestInterface.methodWithArgs("born", 1992));
}

@Test
public void methodWithCustomObject()
{
JUnitTestCustomObject beforeCustomObject = new JUnitTestCustomObject("before", 3);
JUnitTestCustomObject afterCustomObject =
jUnitTestInterface.methodWithCustomObject(beforeCustomObject);
assertEquals("before after", afterCustomObject.getString());
assertEquals(50, afterCustomObject.getI());
}

@Test
public void testMethodReturnList()
{
List<String> list = jUnitTestInterface.methodReturnList("hello", "world");
assertEquals("hello", list.get(0));
assertEquals("world", list.get(1));
}

@Test(expected=JUnitTestCustomException.class)
public void testMethodThrowException()
{
jUnitTestInterface.methodThrowException();
}

@Test(expected=RpcTimeoutException.class)
public void testMethodTimeOut()
{
jUnitTestInterface.methodTimeOut();
}

@Test
public void testMethodReturnVoid()
{
jUnitTestInterface.methodReturnVoid();
}

@Test
public void testMethodHook()
{
integerBeforeHook = 100;
integerAfterHook = 500;
jUnitTestInterface.methodWithoutArg();
assertEquals(101, integerBeforeHook);
assertEquals(501, integerAfterHook);
}

@Test
public void testFutureSuccess()
{
RpcFuture rpcFuture = rpcClientAsyncProxy.call("methodDelayOneSecond");
assertNotNull(rpcFuture);
assertFalse(rpcFuture.isDone());

try
{
Thread.sleep(2000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}

assertTrue(rpcFuture.isDone());
try
{
assertEquals("I have sleep 1000ms already.", rpcFuture.get());
}
catch (Throwable e)
{
e.printStackTrace();
}
}

@Test
public void testFutureError()
{
RpcFuture rpcFuture = rpcClientAsyncProxy.call("methodThrowException");
assertNotNull(rpcFuture);
try
{
rpcFuture.get();
}
catch (Throwable e)
{
if(e instanceof JUnitTestCustomException)
return;

fail(e + " was caught when testFutureError.");
}

fail("failed to catch JUnitTestCustomException.");
}

@Test
public void testFutureListener()
{
//1.test for get a result by RpcFutureListener
countDownLatch = new CountDownLatch(1);

RpcFuture rpcFuture = rpcClientAsyncProxy.call("methodDelayOneSecond");
assertNotNull(rpcFuture);
rpcFuture.setRpcFutureListener(new RpcFutureListener()
{
public void onResult(Object result)
{
countDownLatch.countDown();
}

public void onException(Throwable throwable)
{
fail(throwable + " was caught when testFutureListener.");
}
});

try
{
if(!countDownLatch.await(2000, TimeUnit.MILLISECONDS))
fail("failed to get result by RpcFutureListener.");
}
catch (InterruptedException e)
{
fail(e + " was caught when testFutureListener.");
}

//2.test for get a exception by RpcFutureListener
countDownLatch = new CountDownLatch(1);
rpcFuture = rpcClientAsyncProxy.call("methodThrowException");
assertNotNull(rpcFuture);
rpcFuture.setRpcFutureListener(new RpcFutureListener()
{
public void onResult(Object result)
{
fail("failed to catch JUnitTestCustomException.");
}

public void onException(Throwable throwable)
{
if(throwable instanceof JUnitTestCustomException)
countDownLatch.countDown();
else
fail("failed to catch JUnitTestCustomException.");
}
});

try
{
if(!countDownLatch.await(1000, TimeUnit.MILLISECONDS))
fail("failed to get exception by RpcFutureListener.");
}
catch (InterruptedException e)
{
fail(e + " was caught when testFutureListener.");
}
}

@Test(expected=RpcMethodNotFoundException.class)
public void testMethodNotFound()
{
rpcClientAsyncProxy.call("methodWhichIsNotExist");
}
}
代码量较大,但都简单易懂,对每个方法调用后加上断言判断结果是否符合预期。其中包含了对同步方式和异步方式的测试,异步方式中对RpcFuture和RpcFutureListener也进行了测试。测试的内容包括0个或多个参数,使用自定义类型的参数,返回自定义类型的参数,返回List<T>,方法抛出异常,方法调用超时,方法返回空类型,设置的钩子函数Hook的调用等。由于同步方式直接调用实现了接口的代理对象的方法,不存在找不到方法的情况。而异步方式下使用call()方法进行调用,有可能存在找不到与参数1同名的方法的情况,此时会抛出RpcMethodNotFoundException,因此异步方式下还应对此情景进行测试。因此此功能测试基本涵盖本框架使用的方方面面。

测试结果:

测试全部通过

(3).JUnitMultiThreadSafeTest

为验证RPC客户端是否是线程安全的,进行线程安全测试

package com.maigo.rpc.test;

import static org.junit.Assert.*;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

import org.junit.BeforeClass;
import org.junit.Test;

import com.maigo.rpc.client.RpcClientProxyBuilder;

public class JUnitMultiThreadSafeTest
{
public static JUnitTestInterface jUnitTestInterface;
public final static int THREADS = 20;
public final static int INVOKES = 1000;
public final static int TIMEOUT = 10000;

@BeforeClass
public static void init()
{
jUnitTestInterface = RpcClientProxyBuilder.create(JUnitTestInterface.class)
.timeout(2000)
.threads(4)
.connect("127.0.0.1", 3721)
.build();
}

@Test
public void testMultiThreadSafe()
{
ExecutorService threadPool = Executors.newFixedThreadPool(THREADS);
CountDownLatch countDownLatch = new CountDownLatch(THREADS * INVOKES);
for(int i=0; i<THREADS; i++)
{
threadPool.execute(new JUnitMultiThreadSafeTestThread(i, jUnitTestInterface, countDownLatch));
}

try
{
if(countDownLatch.await(TIMEOUT, TimeUnit.MILLISECONDS))
return;
else
fail("some thread did not finish all the invoke.");
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}

private static class JUnitMultiThreadSafeTestThread extends Thread
{
private int threadId;
private JUnitTestInterface jUnitTestInterface;
private CountDownLatch countDownLatch;

public JUnitMultiThreadSafeTestThread(int threadId, JUnitTestInterface jUnitTestInterface,
CountDownLatch countDownLatch)
{
this.threadId = threadId;
this.jUnitTestInterface = jUnitTestInterface;
this.countDownLatch = countDownLatch;
}

@Override
public void run()
{
for(int i=0; i<INVOKES; i++)
{
int result = jUnitTestInterface.methodForMultiThread(threadId);
if(result == threadId)
countDownLatch.countDown();
}
}
}
}
开启了20个线程使用共享的RpcClient代理对象jUnitTestInterface各进行1000次方法调用。每个线程拥有唯一的线程Id,所调用的方法会直接返回其参数,每个线程中对methodForMultiThread()方法的调用送进本线程的Id,当且仅当methodForMultiThread()方法的返回值等于本线程Id才会让countDownLatch减1。每个线程只会进行1000次调用,故若由于线程不安全导致某次调用失败或调用结果被错误的线程获取到,countDownLatch就不可能递减至0了,此时测试将不通过。而若RPC客户端是线程安全的,则countDownLatch能顺利递减至0,测试通过。

测试结果:

测试通过

(4).RpcPerformanceTest

package com.maigo.rpc.test;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

import com.maigo.rpc.client.RpcClientProxyBuilder;
import com.maigo.rpc.utils.InfoPrinter;

public class RpcPerformanceTest
{
public static JUnitTestInterface jUnitTestInterface;
public final static int THREADS = 16;
public final static int INVOKES = 10000;
public final static int TIMEOUT = 300;

public static void main(String[] args) throws Exception
{
jUnitTestInterface = RpcClientProxyBuilder.create(JUnitTestInterface.class)
.timeout(0)
.threads(4)
.connect("127.0.0.1", 3721)
.build();

ExecutorService threadPool = Executors.newFixedThreadPool(THREADS);
CountDownLatch countDownLatch = new CountDownLatch(THREADS * INVOKES);
InfoPrinter.println("RpcPerformanceTest started.");
long startTime = System.currentTimeMillis();
for(int i=0; i<THREADS; i++)
{
threadPool.execute(new RpcPerformanceTestThread(jUnitTestInterface, countDownLatch));
}

if(countDownLatch.await(TIMEOUT, TimeUnit.SECONDS))
{
long endTime = System.currentTimeMillis();
double tps = THREADS * INVOKES / ((endTime - startTime) / 1000f);
InfoPrinter.println("RpcPerformanceTest finished.");
InfoPrinter.println("Result tps = " + tps);
}
else
{
InfoPrinter.println("RpcPerformanceTest failed.");
}
}

private static class RpcPerformanceTestThread extends Thread
{
private JUnitTestInterface jUnitTestInterface;
private CountDownLatch countDownLatch;

public RpcPerformanceTestThread(JUnitTestInterface jUnitTestInterface,
CountDownLatch countDownLatch)
{
this.jUnitTestInterface = jUnitTestInterface;
this.countDownLatch = countDownLatch;
}

@Override
public void run()
{
for(int i=0; i<INVOKES; i++)
{
jUnitTestInterface.methodForPerformance();
countDownLatch.countDown();
}
}
}
}
开启了16个线程,每个线程进行10000次方法调用。调用结束用调用总数160000除以耗费的时间即可得到每秒处理的调用请求数。

测试结果:

测试环境:酷睿i5-2410m 4G内存 64位Windows7操作系统

[2015-09-21 23:40:05]:Try to connect to [127.0.0.1:3721].
[2015-09-21 23:40:05]:Connect to [127.0.0.1:3721] successed.
[2015-09-21 23:40:05]:RpcPerformanceTest started.
[2015-09-21 23:40:14]:RpcPerformanceTest finished.
[2015-09-21 23:40:14]:Result tps = 16872.298828125


完整的代码提交在:https://github.com/maigo-uestc/Maigo-RPC
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: