Commit 35ed4e0f authored by xie.qin's avatar xie.qin

firstly commit.

parent 9534500f
HELP.md
build/
gradle
!gradle/wrapper/gradle-wrapper.jar
!gradle/wrapper/gradle-wrapper.properties
!**/src/main/**/build/
!**/src/test/**/build/
......@@ -34,3 +36,6 @@ out/
### VS Code ###
.vscode/
/gradle/wrapper/wrapper/
/gradle/
/gradle/wrapper/wrapper/dists/
# Automation Test Framework for Automatic API and Java-SDK Testing
## Automatic API Testing
Depend on Open API technology, This test framework helps you to verify the correctness and compliance of micro-service's exposure APIs.
Especially, it gives great supports in regression tests.
# Automation Test Framework for REST-ful API and Java-SDK Testing
## REST-ful API Testing
Supported by Open API technology, This test framework helps you to verify the correctness and compliance of micro-service's exposure APIs.
Especially, it gives great supports and decreases too much efforts in regression tests .
Assumed we have a simple software product and it has following business call flow:
```FrontEnd_Svc ---- <RESTful Req1> ---- BackEnd_Svc1 ---- <RESTful Req2 which triggered by RESTful Req1> ---- BackEnd_Svc2```
If we have the predefined RESTful API of BackEnd_Svc1 & BackEnd_Svc2, then much repetitive end-to-end verification efforts can be saved.
If we have the predefined REST-ful API of BackEnd_Svc1 & BackEnd_Svc2, then much repetitive end-to-end verification efforts can be saved.
Automation API test consists following 2 parts:
1. Verify the exposed APIs work as design in BackEnd_Svc1 and BackEnd_Svc2
* To have this test framework generated RESTful requests automatically based on API definition.
* To have this test framework generated REST-ful requests automatically based on API definition.
Note:
- For easy to use and test efficiency, except user and organization info, no need to persist more testing data.
- So in the expectation of automation test, GET/PUT method request with a specific ID (it is the maximum value based on the data type) will be failed perhaps, but most of POST method requests should succeed.
- For those optional query parameters in path, requests will be sent out twice. One is with them, another is without them.
- Each field in Json body of request will use the boundary values based on data type.
* After received the response, it will check HTTP status code and json_body with above API definition also.
* Ont only verify the positive call flows, it can generate some negative test cases automatically depend on "parameters" type (e.g. over the boundary).
2. Similarly, we can construct some HTTP servers as mocks when the BackEnd_Svcs are still in developing.
With it, we can verify business logic of FrontEnd_Svc OR triggered behaviour in BackEnd_Svc1, not to care about development progress of Back1End_Svc1 OR BackEnd_Svc2.
\ No newline at end of file
* With it, we can verify business logic of FrontEnd_Svc OR triggered behaviour in BackEnd_Svc1, not to care about development progress of Back1End_Svc1 OR BackEnd_Svc2.
3. Limitation
* All automatically generated requests does not consider business logic. So the assert failure is not real failure, manually double check is preferred.
## Java SDK Testing
\ No newline at end of file
......@@ -15,7 +15,7 @@ import java.util.Map;
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class RestfulMessageModel {
public class RestfulMessage {
private ContentType contentType;
private Headers headers;
......@@ -24,7 +24,9 @@ public class RestfulMessageModel {
private String path;
private Map<String,Object> jsonBody;
private Map<String,Object> reqJsonBody;
private Response response;
private int statusCode;
private Map<String,Object> respJsonBody;
}
package com.fuzamei.autotest.openapi;
public class RestfulMessageEvent {
import org.springframework.context.ApplicationEvent;
public class RestfulMessageEvent extends ApplicationEvent {
private RestfulMessage restfulMsg;
public RestfulMessageEvent(Object source) {
super(source);
}
public RestfulMessage getMsg() {
return restfulMsg;
}
public void setMsg(RestfulMessage restfulMsg) {
this.restfulMsg = restfulMsg;
}
}
package com.fuzamei.autotest.openapi;
import io.qameta.allure.restassured.AllureRestAssured;
import io.restassured.path.json.JsonPath;
import io.restassured.response.Response;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Map;
import static io.restassured.RestAssured.given;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.MatcherAssert.assertThat;
@Component
public class RestfulMessageHandler {
@Slf4j
public class RestfulMessageEventListener {
@EventListener
public void processRestfulMessageEvent(RestfulMessageEvent restfulMessageEvent){
log.debug("subscriber received a notification.");
RestfulMessage restfulMessage = restfulMessageEvent.getMsg();
Response response = given()
.filter(new AllureRestAssured())
.when()
.contentType(restfulMessage.getContentType())
.headers(restfulMessage.getHeaders())
.request(restfulMessage.getMethod(), restfulMessage.getPath())
.then()
.assertThat()
.statusCode(restfulMessage.getStatusCode())
.extract()
.response();
//response format: {code: "baas.err.success", data: {}, message: "OK", status: "OK"}
//assertThat("http status code is not the expectation.", response.getStatusCode(), equalTo(restfulMessage.getStatusCode()));
//check field code in response body
JsonPath jsonPathEvaluator = response.jsonPath();
String respCode = jsonPathEvaluator.get("code");
assertThat("status code in http response is not the expectation.", respCode, equalTo("baas.err.success"));
//response json body only match key, not value
//check field data contains all of needed key
try{
Map<String, Object> respBody = new HashMap<>();
respBody = jsonPathEvaluator.getMap("data");
matchKeyInRespWithExpectation(respBody, restfulMessage.getRespJsonBody());
}
catch (ClassCastException e){
String expectedDataType = restfulMessage.getRespJsonBody().get("type").toString();
matchDataTypeInRespWithExpectation(jsonPathEvaluator.get("data"), expectedDataType);
}
}
/**
"200": {"description": "OK", "schema": {"type": "array", "items": {"type": "object"}}}
"200": {"description": "OK", "schema": {"type": "array","items": {"$ref": "#/definitions/SysDict对象","originalRef": "SysDict对象" }}}
*/
private void matchKeyInRespWithExpectation(Map<String, Object> recvRespBody, Map<String, Object> expectedRespBody) {
}
private void matchDataTypeInRespWithExpectation(Object recvDataType, String expectedDataType) {
boolean matched = false;
switch (expectedDataType) {
case "string":
if (recvDataType instanceof String){
matched = true;
}
break;
case "boolean":
if (recvDataType instanceof Boolean){
matched = true;
}
break;
case "integer":
if (recvDataType instanceof Integer) {
matched = true;
}
break;
case "number":
if (recvDataType instanceof BigDecimal) {
matched = true;
}
}
assertThat("response data type cannot match the expectation.", matched, equalTo(true));
}
}
......@@ -15,4 +15,5 @@ public class BackendServiceProperties {
private String host;
private String port;
private String openApiDefinitionPath;
private String httpSchema;
}
package com.fuzamei.autotest.testdata;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;
@Component
@Configuration
@Slf4j
@Setter
@Getter
public class GetPreparedData {
}
server.port=8080
logging.level.org.springframework=ERROR
logging.level.com.fuzamei.autotest=DEBUG
backEndService.httpSchema="http://"
backEndService.host="172.22.18.152"
backEndService.port="2345"
backEndService.openApiDefinitionPath=""
testRel="2.1.0"
testRel="v2.1.0"
package com.fuzamei.autotest.steps.openApi;
package com.fuzamei.autotest.steps.openapi;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fuzamei.autotest.openapi.ParseOpenApi;
import com.fuzamei.autotest.properties.BackendServiceProperties;
import com.fuzamei.autotest.properties.GlobalProperties;
import io.cucumber.java.en.Given;
......@@ -8,7 +8,8 @@ import io.cucumber.java.en.Then;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import java.io.File;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;
@Slf4j
public class ApiVerification {
......@@ -18,20 +19,16 @@ public class ApiVerification {
@Autowired
private BackendServiceProperties backendServiceProperties;
private String classPath = this.getClass().getClassLoader().getResource("/").getPath();
@Autowired
private ParseOpenApi parseOpenApi;
@Given("^Found OpenAPI definition for {string} service$")
@Given("^Found OpenAPI definition for (.*?) service$")
public void foundOpenApiProfileForTestingVer(String serviceName) throws Throwable {
String release = globalProperties.getTestRel();
String openApiFilePath = classPath + File.separator +
"test" + File.separator +
"resources" + File.separator +
"testingData" + File.separator +
globalProperties.getTestRel() + File.separator +
serviceName + "OpenAPI.json";
log.debug("{} openAPI definition path is: {}", serviceName, openApiFilePath);
switch (serviceName) {
case "backend":
String openApiFilePath = parseOpenApi.getOpenApiFilePathByVersion(serviceName, release);
assertThat(openApiFilePath, is(not(emptyString())));
backendServiceProperties.setOpenApiDefinitionPath(openApiFilePath);
break;
default:
......@@ -39,8 +36,8 @@ public class ApiVerification {
}
}
@Then("^Analyze OpenAPI and send requests to {string} service$")
public void sendReqToTargetService(String serviceName) throws Throwable {
@Then("^Analyze OpenAPI file of (.*?) service and generate REST-ful requests automatically$")
public void parseOpenApiAndSendReqToTargetService(String serviceName) throws Throwable {
String targetServiceApiFilePath = "";
switch (serviceName) {
case "backend":
......@@ -53,8 +50,9 @@ public class ApiVerification {
default:
break;
}
ObjectMapper objectMapper = new ObjectMapper();
assertThat(parseOpenApi.analyzeOpenApiAndGenerateRequests(serviceName, targetServiceApiFilePath), is(Boolean.TRUE));
}
}
package com.fuzamei.autotest.steps.openApi;
package com.fuzamei.autotest.steps.openapi;
public class MockService {
}
package com.fuzamei.autotest.steps.testdata;
public class HandleTestData {
//add-a-user: user-id, user-name
//https://www.cnblogs.com/Marydon20170307/p/13606372.html
}
@api_backend
Feature: API tests for service of backend
@part1
@verifyApi
Scenario: Verify the exposed APIs work as design
Given Found OpenAPI definition for backend service
Then Analyze OpenAPI and send requests to backend service
\ No newline at end of file
When Most basic testing data is ready for backend service
Then Analyze OpenAPI file of backend service and generate REST-ful requests automatically
Then Send requests to backend service and check response
\ No newline at end of file
u_userinfo:
id: 11000000
phone: 13999999999
name: automation
#password: admin
password: F6889AA527EA40FB0A2AECC5A28A694E
mail: xie.qin@33.cn
u_organization:
id: 49d8e2dcb4ed41b5b564ab1608322613
name: automation
address: automation
type: automation
creator: 11000000
owner: 11000000
status: 1
u_org_user:
id: 5678
user_id: 11000000
org_id: 49d8e2dcb4ed41b5b564ab1608322613
status: 0
u_federation:
id: 896bc3c9312a4759a5d5839be79cdda5
name: automation
creator: 11000000
owner: 11000000
{
"u_userinfo": {
"id": 87654321,
"phone": 13999999999,
"name": "automation",
"passwd": "admin",
"password": "F6889AA527EA40FB0A2AECC5A28A694E",
"mail": "xie.qin@33.cn"
},
"u_organization": {
"id": "49d8e2dcb4ed41b5b564ab1608322613",
"name": "automation",
"address": "automation",
"type": "automation",
"creator": 87654321,
"owner": 87654321,
"status": 1
},
"u_org_user": {
"id": 87654321,
"user_id": 87654321,
"org_id": "49d8e2dcb4ed41b5b564ab1608322613",
"status": 0
},
"u_federation": {
"id": "896bc3c9312a4759a5d5839be79cdda5",
"name": "automation",
"creator": 87654321,
"owner": 87654321
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment