这个是我后来写的一本书,http://www.ituring.com.cn/minibook/10775。这个是我后来找到的自动化完美解决方案。
对于一个自动化框架来说,它可以分为上下两层架构,上层关注用户如何使用,脚本语言是什么形式,报告以一种什么方式展出。底层关注的是大量测试用例如何并发式执行,多处理器如何配置等这些用户看不到的东西。这一章我们说下如何构建框架的底层,构建分布式并发执行测试脚本功能。
今天这一节我们先给出第一个解决方案。rmi+jms处理方式。
首先说下rmi。它是搭建java分布式的基础,在java1.1中已经存在这个类,他是分布式及后来的webservice的基础。corejava7和8在第二卷高级篇中对它都有介绍,感兴趣的朋友可以先对着core java教程做下demo再看下面解决方案。当然最好用两台机子,没有两台机子的话你可以装个虚拟机。
假设我们有一个主机,几个分机,那我们来搭建一下的它的环境。
首先我们搭建一个RMI的接口,无论是客户端或者是服务器端都要继承这个接口,这个接口继承Rmote接口。
public interface BertRmiInterface extends Remote
写出它要实现的方法
/**
* The core method that executing test.
public List<BertResult> execute(String area, int maxThreads, String outputDir, String logLevel, String qaEnv, BertCrampon ceng) throws RemoteException; // run test
public String getBertRoot()throws RemoteException;
/**
* Terminate this testing process(jvm).
*/
public void exit() throws RemoteException; //self terminating
/**
* to start Selenium RC
* @return
* @throws java.rmi.RemoteException
*/
public int startRemoteControl() throws RemoteException;
/**
* to stop Selenium RC
* @param port
* @throws java.rmi.RemoteException
*/
public void stopRemoteControl(int port) throws RemoteException;
/**
* upload dir to a shared folder
* @param outputDir
* @param remoteDir
* @throws java.rmi.RemoteException
*/
public void uploadOutput(String outputDir, String remoteDir) throws RemoteException;
/**
* call cmd in remote machine
* @param cmd
* @throws java.rmi.RemoteException
*/
public int execCMD(String cmd) throws RemoteException;
//may needed in future
//public int getInvokingCount() throws RemoteException; // how busy am I
//public void resetEnv()throws RemoteException; // for reuse
}
接下来我们搭建服务器环境,它实现了接口的所有方法。注意里面的main()方法,它负责创建定位rmi主服务器对象。并绑定端口地址供客户端访问。
public class BertRmiServer extends UnicastRemoteObject implements BertRmiInterface{
//这里设置线程的结束。
public void exit() throws RemoteException{
System.out.println("###RmiServer-["+boundName+"] - Terminiting server process****");
Thread t = new Thread() {
public void run() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
}
System.exit(0);
}
};
t.start();
}
/**
* This "remote" method to execute real testing, main steps:
* 1,把管理好的测试列表加载进去,2.为每个测试用例开启一个线程。3,创建报告。
*
* @return The result of this testing.
* @param qaEnv QA environment
* @param logLevel log level
* @param area Business area to test
* @param maxThreads Maximum concurrent BERT testing threads allowed
* @param outputDir Output directory, for log & report
* @throws java.rmi.RemoteException Required by RMI
*/
public List<BertResult> execute(String area, int maxThreads, String outputDir, String logLevel, String qaEnv, BertCrampon ceng) throws RemoteException {
System.out.println("###RmiServer-[" + area + "] - begins to run with maxThreads " + maxThreads + " ****");
this.logLevel = logLevel;
Date start = new Date();
Engine engine = new Engine();
engine.startBertEngine(area, maxThreads, outputDir, logLevel, qaEnv,ceng);
Date end = new Date();
List<BertResult> retList = null;
retList = new ArrayList<BertResult>();
AreaReport rpt = new AreaReport(new ArrayList(BertParser.testcases.values()), outputDir + "reports/", area, start, end);
rpt.startAreaReport();
retList.add(rpt.getResult());
return retList;
}
/**
* To parse the CMD line arguments.
*/
private void parseCmdlineArgs(String[] args){
if( args.length < 1 ){
System.out.println("###BertRmiServer- Error, at least 1 param required(RMI bound name), exit now...");
System.exit(1);
}
boundName = args[0];
}
/**
* The main method of the RMI server application, main flow:
* 1.create an instance of BertRmiServer,
* 2.bind it to a certain name and automatically begin RMI listening
* @param args CMD line arguments, at least one argument required - the RMI binding name
*/
public static void main(String[] args) {
try {
BertRmiServer serverobj = new BertRmiServer();
serverobj.parseCmdlineArgs(args);
BertController.enableJMSMessaging();
Context c = new InitialContext();
c.bind("rmi:"+ boundName, serverobj);
System.out.println("###BertRmiServer-["+boundName+"] - begin listening****");
} catch (RemoteException ex) {
ex.printStackTrace();
} catch (NamingException ex) {
ex.printStackTrace();
}
}
/**
* start Selenium RC
* @return
* @throws java.rmi.RemoteException
*/
public int startRemoteControl() throws RemoteException {
JstSeleniumRC jstRC= new JstSeleniumRC();
jstRC.setIsEmbededRC(false);
try {
jstRC.start();
} catch (ProcessingErrorException ex) {
return -500;
}
this.jstServers.put(jstRC.getPortNumber()+"",jstRC);
return jstRC.getPortNumber();
}
/**
* stop Selenium RC
*/
public void stopRemoteControl(int port) throws RemoteException {
JstSeleniumRC jstRC = this.jstServers.get(port + "");
if (jstRC != null) {
jstRC.stop();
this.jstServers.remove(port + "");
}
}
/**
* upload dir to a shared folder
* @param outputDir
* @param remoteDir
* @throws java.rmi.RemoteException
*/
public void uploadOutput(String outputDirBase, String remoteDir) throws RemoteException {
String[] cmd = null;
try {
if (OS_NAME.contains("Windows")) {
String outputDirFull = BertBase.getBertRoot() + "var" + File.separator + "output" + File.separator + outputDirBase;
cmd = new String[]{"xcopy", outputDirFull, remoteDir, "/EYI"};
} else {
cmd = new String[]{BertBase.getBertBinRoot() + "samba-upload.sh", outputDirBase};
}
Runtime.getRuntime().exec(cmd);
} catch (Exception ex) {
System.out.println(" !!! error when uploadOutput: " + ex);
ex.printStackTrace();
}
}
/**
* call cmd in remote machines
* @param cmd
* @throws java.rmi.RemoteException
*/
public int execCMD(String cmd) throws RemoteException {
try {
Runtime.getRuntime().exec(cmd);
return 0;
} catch (IOException ex) {
System.out.println(" !!! error when execute command: " + cmd + " => "+ ex);
return -1;
}
}
// get BERT_R0OT from rmi call dynamically
public String getBertRoot() throws RemoteException {
return BertBase.getBertRoot();
}
}
这里面把接口的所有方法给实现了,客户端只要把接口的方法通过Naming.lookup查找到这个服务器。就可以把服务器里面对接口的实现方法传到客户端。客户端的就自然的获得的service传回的数据了。
因为运行case是多线程启动运行的。所以客户端绑定了线程。
public class RmiClientThread implements Runnable{
public void run(){
String url = "//localhost/";
try{
StreamGobbler sg = new StreamGobbler(serverp.getInputStream());
sg.start();
BertRmiInterface br = (BertRmiInterface) lookupRmi(url + this.area, 30*1000, 100);
if (br==null){
List<BertResult> results = br.execute(this.area, this.maxThreads, this.outputDir, this.logLevel, this.qaEnv, this.ceng); // call remote method
}catch (Exception ex){
this.clientThreadLogger.error(this.getClass().getName()+" - ", ex);
}
}
}
public static Remote lookupRmi(String name, int timeout, int interval) {
System.out.println(BertRmiClientThread.class.getName() + " begin to lookup rmi: " + name);
int loops = timeout/interval>0 ? timeout/interval:1; // at least once
int i = 0;
boolean success = false;
Remote br = null;
for(; i<loops && (!success); i++){
try {
Thread.sleep(interval);
br = Naming.lookup(name);
} catch (Exception ex) {
continue; //look up again
}
success = true;
}
if (success){
System.out.println(RmiClientThread.class.getName() + " RMI lookup OK : " + name);
} else {
System.out.println(RmiClientThread.class.getName() + " RMI lookup failed : " + name);
}
return br;
}
到这里的话我们主机和分机之间的分布式架构就架设好了,那执行的时候如何设定它们之间的通信呢,现在开始说下jms
首先,定义一个rmi接口
public interface MessageManagerInterface extends Remote {
/** RMI bound name */
static final String BOUND_NAME = "msgManager";
/** Config key for msg manager host */
static final String MSG_MANAGER_RMI_HOST = "MSG_MANAGER_RMI_HOST";
/** Config key for msg manager port */
static final String MSG_MANAGER_RMI_PORT = "MSG_MANAGER_RMI_PORT";
/**
* Remote method that clients can call to send messages to the message queue.
*
* @param msg Message
* @throws RemoteException Required by RMI
*/
void sendMessage(String msg) throws RemoteException;
}
制定消息发送者类实现这个接口。
public class BertMessageManager extends UnicastRemoteObject implements MessageManagerInterface {
/** Message queue sender */
private QueueSender queueSender = null;
/** Internal queue to act as a buffer in case the JMS message queue fails. */
private ArrayBlockingQueue<String> internalQueue = new ArrayBlockingQueue<String>(QUEUE_MAX_SIZE);
/** Internal queue maximum size */
private static final int QUEUE_MAX_SIZE = 500;
public MessageManager(String[] args) throws RemoteException
{
//读取配置文件MSG_MANAGER_RMI_PORT
int port = Integer.parseInt(Base.getConfig(MSG_MANAGER_RMI_PORT));
RmiClient.startRmiregistry(port, msgManagerLogger);
initSenderThread();
}
private void initSenderThread()
{
Thread senderThread = new Thread(new Runnable()
{
public void run()
{
while(true)
{
initMessageQueue();
String msg = null;
try
{
// take method will block until the queue has some entry
msg = internalQueue.take();
QueueSession queueSession = MessageUtil.getQueueSession();
TextMessage myTextMsg = queueSession.createTextMessage();
myTextMsg.setText(msg);
queueSender.send(myTextMsg);
// Since the message was sent successfully,
// reset the failure email sent flag.
MessageUtil.resetFailureEmailSentFlag();
}
catch (InterruptedException e)
{
msgManagerLogger.error(this.getClass().getName() + " - ", e);
}
catch (JMSException jmse)
{
msgManagerLogger.error(this.getClass().getName() +
" - Error while sending message to the message queue", jmse);
// Send email to jbert about this.
BertMessageUtil.sendEmail(jmse);
queueSender = null;
// Put the msg back on the internal queue
addMessageToInternalQueue(msg);
}
}
}
});
senderThread.start();
}
private void initMessageQueue()
{
while(queueSender == null)
{
MessageUtil.initMessageQueue();
queueSender = BertMessageUtil.initSender();
// If queueSender is still null, sleep for 10 seconds before
// attempting to connect again so that the this thread
// doesn't take up too much of the CPU cycles
if(queueSender == null)
{
try {
Thread.sleep(10000);
} catch(InterruptedException e) {
msgManagerLogger.error(this.getClass().getName() + " - ", e);
}
}
}
}
public void sendMessage(String msg) throws RemoteException
{
if(internalQueue.remainingCapacity() > 0)
{
internalQueue.add(msg);
}
}
public static MessageManagerInterface lookupMessageManager()
{
String host = Base.getConfig(MSG_MANAGER_RMI_HOST);
String port = Base.getConfig(MSG_MANAGER_RMI_PORT);
return (MessageManagerInterface) RmiClientThread.lookupRmi(
"//" + host + ":" + port + "/" + BOUND_NAME, 100, 100);
}
/**
* The main method of the RMI server application, main flow:
* 1. Create an instance of BertMessageManager,
* 2. Bind it to a certain name and automatically begin RMI listening
*
* @param args Command line arguments
* @throws NamingException
* @throws RemoteException
*/
当下面程序启动,他就会发送信息。
public static void main(String[] args) throws NamingException, RemoteException {
MessageManager msgManager = new MessageManager(args);
String host = Base.getConfig(MSG_MANAGER_RMI_HOST);
String port = Base.getConfig(MSG_MANAGER_RMI_PORT);
Context c = new InitialContext();
c.bind("rmi://" + host + ":" + port + "/"+ BOUND_NAME, msgManager);
msgManager.logInfo("["+ BOUND_NAME +"] - begin listening****");
}
}
消息接受者接收发送者发到队列的消息。
public class MessageConsumer {
/** Queue receiver */
private QueueReceiver queueReceiver = null;
/** Message consumer logger */
private Logger msgConsumerLogger = null;
/**
* Constructor for BertMessageConsumer
*
* @param args Command line arguments
*/
public MessageConsumer(String[] args)
{
MessageUtil.initialize();
receive();
}
/**
* Initializes the message queue.
*/
private void initMessageQueue()
{
while(queueReceiver == null)
{
MessageUtil.initMessageQueue();
queueReceiver = MessageUtil.initReceiver();
// If queueReceiver is still null, sleep for 10 seconds before
// attempting to connect again so that the this thread
// doesn't take up too much of the CPU cycles
if(queueReceiver == null)
{
try {
Thread.sleep(10000);
} catch(InterruptedException e) {
this.msgConsumerLogger.error(this.getClass().getName() + " - ", e);
}
}
}
}
/**
* Receives the messages from the message queue.
*/
private void receive()
{
while(true)
{
try
{
initMessageQueue();
Message msg = queueReceiver.receive();
if (msg instanceof TextMessage) {
TextMessage txtMsg = (TextMessage) msg;
this.msgConsumerLogger.info(this.getClass().getName() + " - " + txtMsg.getText());
}
// Since the message was received successfully,
// reset the failure email sent flag.
MessageUtil.resetFailureEmailSentFlag();
}
catch(JMSException e)
{
this.msgConsumerLogger.error(this.getClass().getName() + " - ", e);
tMessageUtil.sendEmail(e);
queueReceiver = null;
}
}
}
/**
* Main method. Calls the BertMessageManager constructor.
*
* @param args Command line arguments.
*
*/
public static void main(String[] args)
{
new MessageConsumer(args);
}
}
最后这个工具类实现jms的接口函数供前面发送者接受者调用。
public class MessageUtil
{
/** JNDI Context */
private static Context ctx = null;
/** Message queue connection factory */
private static QueueConnectionFactory qcf = null;
/** Message queue connection */
private static QueueConnection queueConnection = null;
/** Message queue session */
private static QueueSession queueSession = null;
/** Message queue */
private static Queue queue = null;
/** Message queue sender */
private static QueueSender queueSender = null;
/** Message queue sender */
private static QueueReceiver queueReceiver = null;
/** Logger */
private static Logger logger = null;
/** Flag to indicate whether email has been sent about message queue failure */
private static boolean failureEmailSent = false;
/** Output directory */
private static String outputDir = null;
/** Message queue connection factory lookup name for jbert */
public static final String CF_LOOKUP_NAME = "cn=ConnectionFactory";
/** Message queue lookup name for jbert */
public static final String QUEUE_LOOKUP_NAME = "cn=automationtool";
/**
* Initializes message queue.
*/
protected static void initMessageQueue()
{
// Reset the static variables
ctx = null;
qcf = null;
queueConnection = null;
queue = null;
queueSession = null;
queueSender = null;
queueReceiver = null;
config.load(new FileInputStream(CONF_ROOT + "appproperties.properties"));
if(ctx != null) { MessageUtil.initConnectionFactory(); }
if(qcf != null) { MessageUtil.initQueue(); }
if(queue != null) { MessageUtil.initConnection(); }
if(queueConnection != null) { MessageUtil.initSession(); }
}
/**
* Initializes the context
*/
private static void initContext()
{
Hashtable env = new Hashtable();
String ldapUrl = Base.getConfig("JMS_LDAP_URL");
// Store the environment variables that tell JNDI which initial context
// to use and where to find the provider.
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, ldapUrl);
try {
// Create the initial context.
ctx = new InitialContext(env);
} catch (NamingException ne) {
sendEmail(ne);
}
}
/**
* Initializes the connection factory
*/
private static void initConnectionFactory()
{
try {
// Lookup my connection factory from the admin object store.
// The name used here here must match the lookup name
// used when the admin object was stored.
logger.info(MessageUtil.class.getName() + " - Looking up Queue Connection Factory object with lookup name: "
+ CF_LOOKUP_NAME);
qcf = (javax.jms.QueueConnectionFactory) ctx.lookup(CF_LOOKUP_NAME);
logger.info(BertMessageUtil.class.getName() + " - Queue Connection Factory object found.");
} catch (NamingException ne) {
sendEmail(ne);
}
}
/**
* Initializes the queue
*/
private static void initQueue()
{
try {
// Lookup the queue from the admin object store.
// The name used here must match the lookup name used when
// the admin object was stored.
logger.info(BertMessageUtil.class.getName() + " - Looking up Queue object with lookup name: "
+ JBERTQUEUE_LOOKUP_NAME);
queue = (javax.jms.Queue)ctx.lookup(JBERTQUEUE_LOOKUP_NAME);
logger.info(BertMessageUtil.class.getName() + " - Queue object found.");
} catch (NamingException ne) {
sendEmail(ne);
}
}
/**
* Initializes the connection
*/
private static void initConnection()
{
try {
queueConnection = qcf.createQueueConnection();
} catch (JMSException e) {
sendEmail(e);
}
}
/**
* Initializes the queue session
*
* @return Queue session
*/
private static void initSession()
{
try {
queueSession = queueConnection.createQueueSession(false, Session.AUTO_ACKNOWLEDGE);
} catch (JMSException e) {
sendEmail(e);
}
}
/**
* Initializes the sender
*
* @return QueueSender
*/
protected static QueueSender initSender()
{
if(queueSession != null)
{
try {
// Create the queueSender
queueSender = queueSession.createSender(queue);
// Tell the provider to start sending messages.
queueConnection.start();
} catch (JMSException e) {
logger.error(MessageUtil.class.getName() + " - JMS Exception: " , e);
sendEmail(e);
}
}
return queueSender;
}