我需要使用 Cucumber 和 Java 对我的 REST Web 服务执行负载测试。此 REST Web 服务接受一个名为 id 的字符串输入并返回复杂的 JSON 对象。
我编写了一个 .feature 文件,其中包含在 java 中定义的 Given、When 和 Then 注释。类和注释的骨架定义在下面。
1)功能(UserActivity.feature)
@functional @integration
Feature: User System Load Test
Scenario Outline: Load test for user data summary from third party UserSystem
Given Simultaneously multiple users are hitting XYZ services with an id=<ids>
When I invoke third party link with above id for multiple users simultaneously
Then I should get response code and response message for all users
Examples:
| ids |
| "pABC123rmqst" |
| "fakXYZ321rmv" |
| "bncMG4218jst" |
2)LoadTestStepDef.java(特征定义)
package com.system.test.cucumber.steps;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.junit.runners.model.InitializationError;
import com.system.test.restassured.LoadTestUtil;
import cucumber.api.java.en.Given;
import cucumber.api.java.en.Then;
import cucumber.api.java.en.When;
public class LoadTestStepDef
{
private static Logger LOG = LogManager.getLogger( LoadTestStepDef.class );
private String id = null;
private LoadTestUtil service = null;
@Given("^Simultaneously multiple users are hitting XYZ services with an a id=\"(.*?)\"$" )
public void Simultaneously_multiple_users_are_hitting_XYZ_services_with_a_id( String id )
{
LOG.debug( "ID {}", id );
LOG.info( "ID {}", id );
this.id = id;
}
@When( "^I invoke third party link with above id for multiple users simultaneously$" )
public void invoke_third_party_link_With_Above_ID_for_multiple_users_simultaneously() throws InitializationError
{
LOG.debug( " *** Calling simulatenously {} ", id );
LOG.info( " *** Calling simulatenously {}", id );
//Create object of service
service = new LoadTestUtil();
//Set the id to the created service and invoke method
service.setData(id);
service.invokeSimultaneosCalls(10);
}
@Then( "^I should get response code and response message for all users$" )
public void Should_get_response_code_and_response_message_for_all_users()
{
LOG.info( "*** Assert for response Code" );
service.assertHeaderResponseCodeAndMessage();
}
}
3) LoadTestUtil.java
package com.system.test.restassured;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import java.util.concurrent.Callable;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import com.jayway.restassured.path.json.JsonPath;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
public class LoadTestUtil
{
private String id = null;
private int numberofTimes;
//Create List to hold all Future<Long>
private List<JsonPath> jsonResponseList = new ArrayList<JsonPath>();
//No arg Constructor
public LoadTestUtil()
{
}
//Set data method to set the initial id
public void setData(String id)
{
LOG.info( "LoadTestUtil.setData()", id );
this.id = id;
}
//This method is used call the REST webservice N times using threads and get response
public void invokeSimultaneosCalls(int numberofTimes)
{
LOG.info( "LoadTestUtil.invokeSimultaneosCalls() - Start" );
this.numberofTimes = numberofTimes;
try
{
long start = System.nanoTime();
int numberOfThreads = Runtime.getRuntime().availableProcessors();
LOG.info("Number of processor available {}" , numberOfThreads);
//Create pool for the Executor Service with numberOfThreads.
ExecutorService executor = Executors.newFixedThreadPool(numberOfThreads);
//Create a list to hold the Future object associated with Callable
List<Future<JsonPath>> futureList = new ArrayList<Future<JsonPath>>();
//Create new RESTServiceCallTask instance
Callable<JsonPath> callable = new RESTServiceCallTask(id);
Future<JsonPath> future = null;
//Iterate N number of times to submit the callable object
for(int count=1; count<=numberofTimes;count++)
{
//Submit Callable tasks to the executor
future = executor.submit(callable);
//Add Future to the list to get return value using Future
futureList.add(future);
}
//Create a flag to monitor the thread status. Check whether all worker threads are completed or not
boolean threadStatus = true;
while (threadStatus)
{
if (future.isDone())
{
threadStatus = false;
//Iterate the response obtained from the futureList
for(Future<JsonPath> futuree : futureList)
{
try
{
//print the return value of Future, notice the output delay in console
// because Future.get() waits for task to get completed
JsonPath response = futuree.get();
jsonResponseList.add(response);
}
catch(InterruptedException ie)
{
ie.printStackTrace();
}
catch(ExecutionException ee)
{
ee.printStackTrace();
}
catch(Exception e)
{
e.printStackTrace();
}
}//End of for to iterate the futuree list
} //End of future.isDone()
} //End of while (threadStatus)
//shut down the executor service now
executor.shutdown();
//Calculate the time taken by the threads for execution
executor.awaitTermination(1, TimeUnit.HOURS); // or longer.
long time = System.nanoTime() - start;
logger.info("Tasks took " + time/1e6 + " ms to run");
long milliSeconds = time / 1000000;
long seconds, minutes, hours;
seconds = milliSeconds / 1000;
hours = seconds / 3600;
seconds = seconds % 3600;
seconds = seconds / 60;
minutes = seconds % 60;
logger.info("Task took " + hours + " hours, " + minutes + " minutes and " + seconds + " seconds to complete");
} //End of try block
catch (Exception e)
{
e.printStackTrace();
}
LOG.info("LoadTestUtil.invokeSimultaneosCalls() - jsonResponseList {} " , jsonResponseList);
System.out.println("LoadTestUtil.invokeSimultaneosCalls() - jsonResponseList {} " + jsonResponseList);
LOG.info( "*** LoadTestUtil.invokeSimultaneosCalls() - End" );
}
public void assertHeaderResponseCodeAndMessage(){
//Number of response objects available
int size = jsonResponseList.size();
LOG.info("Number of REST service calls made = ", size);
for(JsonPath jsonResponse : jsonResponseList)
{
String responseCode = jsonResponse.get( "header.response_code").toString();
String responseMessage = jsonResponse.get( "header.response_message").toString();
assertEquals( "200", responseCode);
assertEquals( "success", responseMessage);
}
}
}
4) RESTServiceCallTask.java
此类实现 Callable 并覆盖 call() 方法。在 call() 方法中,每次调用都会返回 JsonPath 形式的响应
package com.system.test.restassured;
import static com.jayway.restassured.RestAssured.basePath;
import static com.jayway.restassured.RestAssured.baseURI;
import static com.jayway.restassured.RestAssured.given;
import static com.jayway.restassured.RestAssured.port;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import com.system.test.restassured.TestUtil;
import com.jayway.restassured.path.json.JsonPath;
import com.jayway.restassured.response.Response;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
public class RESTServiceCallTask implements Callable<JsonPath>
{
private static Logger LOG = LogManager.getLogger(RESTServiceCallTask.class);
private Response response = null;
private String id;
private String environment;
//private JsonPath jsonPath;
/**
* Constructor initializes the call to third party system
*
* @param id
*/
public RESTServiceCallTask(String id)
{
LOG.info("In RESTServiceCallTask() constructor ");
this.id = id;
//Read the environment variable ENV to get the corresponding environment's REST URL to call
this.environment = System.getProperty("ENV");
baseURI = TestUtil.getbaseURL(environment);
basePath = "/bluelink/tracker/member_summary";
port = 80;
LOG.info(" *** Environment : {}, URI: {} and Resource {} ", environment, baseURI, basePath);
}
//This method is called by the threads to fire the REST service and returns JSONPath for each execution
@Override
public JsonPath call() throws Exception
{
LOG.info(" *** In call() method ");
try
{
response = given().headers("id", this.id).log().all().get();
} catch (Exception e)
{
LOG.error("System Internal Server Error", e);
}
String strResponse = this.response.asString();
LOG.info("Response : {}", strResponse);
JsonPath jsonResponse = new JsonPath(strResponse);
return jsonResponse;
}
}
5)TestUtil.java
该实用程序类用于获取传递的环境对应的 REST URL
package com.system.test.restassured;
import java.util.HashMap;
import java.util.Map;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class TestUtil
{
private static Logger LOG = LogManager.getLogger(TestUtil.class);
private static final Map<String, String> ENVIRONMENT_MAP = new HashMap<String, String>();
static
{
ENVIRONMENT_MAP.put("LOCAL", "http://localhost:9080");
ENVIRONMENT_MAP.put("ENV1", "http://localhost:9080");
ENVIRONMENT_MAP.put("ENV2", "http://localhost:9080");
ENVIRONMENT_MAP.put("ENV3", "http://localhost:9080");
}
public static String getbaseURL(String environment)
{
LOG.info("Environment value fetched = {}", environment);
return ENVIRONMENT_MAP.get(environment);
}
}
这里的问题是多线程功能没有得到执行。我使用了 MavenSurefire 插件并尝试了并行类和方法。在这些情况下,上述情况也不起作用。
Cucumber 支持 java 多线程吗?如果是这样,上述特征定义有什么问题?
注意 - 使用独立程序执行相同的任务,并且能够使用 4 个线程运行 10,000 次而没有任何问题。但是无法使用 Maven 运行上述代码 2000 次。用了2000次,系统突然崩溃。
我正在使用 Rational Application Developer 8.5、Websphere Server 8.0 和 Maven 3.x 进行上述设置。
感谢您的答复。