fix(接口测试): 接口定义用例数统计和模块显示不正确

This commit is contained in:
AgAngle 2024-05-17 14:51:39 +08:00 committed by Craftsman
parent 1248028981
commit 5f6299b8d5
10 changed files with 87 additions and 71 deletions

View File

@ -3,7 +3,6 @@ package io.metersphere.api.controller.definition;
import com.fasterxml.jackson.databind.node.TextNode; import com.fasterxml.jackson.databind.node.TextNode;
import com.github.pagehelper.Page; import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageHelper;
import io.metersphere.api.constants.ApiResourceType;
import io.metersphere.api.domain.ApiDefinition; import io.metersphere.api.domain.ApiDefinition;
import io.metersphere.api.dto.ReferenceDTO; import io.metersphere.api.dto.ReferenceDTO;
import io.metersphere.api.dto.ReferenceRequest; import io.metersphere.api.dto.ReferenceRequest;

View File

@ -45,6 +45,9 @@ public class ApiDefinitionDTO extends ApiDefinition{
@Schema(description = "是否关注") @Schema(description = "是否关注")
private Boolean follow; private Boolean follow;
@Schema(description = "模块名称")
private String moduleName;
@Schema(description = "自定义字段集合") @Schema(description = "自定义字段集合")
private List<ApiDefinitionCustomFieldDTO> customFields; private List<ApiDefinitionCustomFieldDTO> customFields;

View File

@ -9,6 +9,7 @@ import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size; import jakarta.validation.constraints.Size;
import lombok.Data; import lombok.Data;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
@ -81,6 +82,10 @@ public class CsvVariable {
return StringUtils.isNotBlank(name) && file != null; return StringUtils.isNotBlank(name) && file != null;
} }
public boolean isEnable() {
return BooleanUtils.isTrue(enable);
}
public enum CsvEncodingType implements ValueEnum { public enum CsvEncodingType implements ValueEnum {
UTF8("UTF-8"), UFT16("UTF-16"), ISO885915("ISO-8859-15"), US_ASCII("US-ASCII"), GBK("GBK"); UTF8("UTF-8"), UFT16("UTF-16"), ISO885915("ISO-8859-15"), US_ASCII("US-ASCII"), GBK("GBK");
private String value; private String value;

View File

@ -2,6 +2,7 @@ package io.metersphere.api.mapper;
import io.metersphere.api.domain.ApiDefinition; import io.metersphere.api.domain.ApiDefinition;
import io.metersphere.api.domain.ApiDefinitionCustomField; import io.metersphere.api.domain.ApiDefinitionCustomField;
import io.metersphere.api.domain.ApiTestCase;
import io.metersphere.api.dto.ApiDefinitionExecuteInfo; import io.metersphere.api.dto.ApiDefinitionExecuteInfo;
import io.metersphere.api.dto.ReferenceDTO; import io.metersphere.api.dto.ReferenceDTO;
import io.metersphere.api.dto.ReferenceRequest; import io.metersphere.api.dto.ReferenceRequest;
@ -23,7 +24,7 @@ public interface ExtApiDefinitionMapper {
List<ApiDefinitionDTO> listDoc(@Param("request") ApiDefinitionDocRequest request); List<ApiDefinitionDTO> listDoc(@Param("request") ApiDefinitionDocRequest request);
List<ApiCaseComputeDTO> selectApiCaseByIdsAndStatusIsNotTrash(@Param("ids") List<String> ids, @Param("projectId") String projectId); List<ApiTestCase> selectNotInTrashCaseIdsByApiIds(@Param("apiIds") List<String> apiIds);
Long getPos(@Param("projectId") String projectId); Long getPos(@Param("projectId") String projectId);

View File

@ -46,27 +46,11 @@
<include refid="queryDocWhereCondition"/> <include refid="queryDocWhereCondition"/>
</select> </select>
<select id="selectApiCaseByIdsAndStatusIsNotTrash" <select id="selectNotInTrashCaseIdsByApiIds" resultType="io.metersphere.api.domain.ApiTestCase">
resultType="io.metersphere.api.dto.definition.ApiCaseComputeDTO"> select atc.id, atc.api_definition_id
select FROM api_test_case atc
t1.api_definition_id apiDefinitionId, WHERE atc.deleted = 0 and atc.api_definition_id IN
count( t1.id ) caseTotal, <foreach collection="apiIds" item="value" separator="," open="(" close=")">
SUM( CASE WHEN t2.`status` = 'SUCCESS' THEN 1 ELSE 0 END ) AS `success`,
SUM( CASE WHEN t2.`status` = 'ERROR' THEN 1 ELSE 0 END ) AS `error`,
SUM( CASE WHEN t2.`status` = 'FAKE_ERROR' THEN 1 ELSE 0 END ) AS `fakeError`,
CONCAT( FORMAT( SUM( IF ( t2.`status` = 'SUCCESS', 1, 0 ))/ COUNT( t1.id )* 100, 2 ), '%' ) casePassRate
FROM
api_test_case t1
LEFT JOIN api_test_case_record t3 on t1.id = t3.api_test_case_id
LEFT JOIN api_report t2 ON t2.id = t3.api_report_id
WHERE
t1.project_id = #{projectId} and t1.deleted = 0
GROUP BY
t1.api_definition_id
HAVING
t1.api_definition_id IN
<foreach collection="ids" item="value" separator="," open="(" close=")">
#{value} #{value}
</foreach> </foreach>
</select> </select>

View File

@ -1,5 +1,6 @@
package io.metersphere.api.mapper; package io.metersphere.api.mapper;
import io.metersphere.api.domain.ApiDefinitionModule;
import io.metersphere.api.dto.debug.ApiTreeNode; import io.metersphere.api.dto.debug.ApiTreeNode;
import io.metersphere.api.dto.definition.ApiModuleRequest; import io.metersphere.api.dto.definition.ApiModuleRequest;
import io.metersphere.project.dto.ModuleCountDTO; import io.metersphere.project.dto.ModuleCountDTO;
@ -36,4 +37,6 @@ public interface ExtApiDefinitionModuleMapper {
List<BaseTreeNode> selectBaseByIds(@Param("ids") List<String> ids); List<BaseTreeNode> selectBaseByIds(@Param("ids") List<String> ids);
List<String> getModuleIdsByParentIds(@Param("parentIds") List<String> parentIds); List<String> getModuleIdsByParentIds(@Param("parentIds") List<String> parentIds);
List<ApiDefinitionModule> getNameInfoByIds(@Param("ids") List<String> ids);
} }

View File

@ -123,6 +123,14 @@
#{parentId} #{parentId}
</foreach> </foreach>
</select> </select>
<select id="getNameInfoByIds" resultType="io.metersphere.api.domain.ApiDefinitionModule">
SELECT id, name
FROM api_definition_module
WHERE id IN
<foreach collection="ids" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</select>
<sql id="api_request"> <sql id="api_request">
<where> <where>

View File

@ -47,7 +47,7 @@ public class MsCsvChildPreConverter extends AbstractJmeterElementConverter<Abstr
} }
private static void addCsvDataSet(HashTree tree, String shareMode, CsvVariable csvVariable) { private static void addCsvDataSet(HashTree tree, String shareMode, CsvVariable csvVariable) {
if (!csvVariable.isValid()) { if (!csvVariable.isValid() || !csvVariable.isEnable()) {
return; return;
} }
// 执行机执行文件存放的缓存目录 // 执行机执行文件存放的缓存目录

View File

@ -25,10 +25,7 @@ import io.metersphere.project.mapper.ExtBaseProjectVersionMapper;
import io.metersphere.project.service.EnvironmentService; import io.metersphere.project.service.EnvironmentService;
import io.metersphere.project.service.MoveNodeService; import io.metersphere.project.service.MoveNodeService;
import io.metersphere.project.service.ProjectService; import io.metersphere.project.service.ProjectService;
import io.metersphere.sdk.constants.ApiFileResourceType; import io.metersphere.sdk.constants.*;
import io.metersphere.sdk.constants.ApplicationNumScope;
import io.metersphere.sdk.constants.DefaultRepositoryDir;
import io.metersphere.sdk.constants.ModuleConstants;
import io.metersphere.sdk.domain.OperationLogBlob; import io.metersphere.sdk.domain.OperationLogBlob;
import io.metersphere.sdk.dto.api.task.TaskRequestDTO; import io.metersphere.sdk.dto.api.task.TaskRequestDTO;
import io.metersphere.sdk.exception.MSException; import io.metersphere.sdk.exception.MSException;
@ -79,6 +76,9 @@ public class ApiDefinitionService extends MoveNodeService {
@Resource @Resource
private ExtApiDefinitionMapper extApiDefinitionMapper; private ExtApiDefinitionMapper extApiDefinitionMapper;
@Resource
private ExtApiDefinitionModuleMapper extApiDefinitionModuleMapper;
@Resource @Resource
private ApiDefinitionFollowerMapper apiDefinitionFollowerMapper; private ApiDefinitionFollowerMapper apiDefinitionFollowerMapper;
@ -117,8 +117,6 @@ public class ApiDefinitionService extends MoveNodeService {
@Resource @Resource
private ApiDefinitionLogService apiDefinitionLogService; private ApiDefinitionLogService apiDefinitionLogService;
@Resource
private ApiDefinitionImportUtilService apiDefinitionImportUtilService;
@Resource @Resource
private ApiDefinitionMockService apiDefinitionMockService; private ApiDefinitionMockService apiDefinitionMockService;
@ -136,9 +134,7 @@ public class ApiDefinitionService extends MoveNodeService {
public List<ApiDefinitionDTO> getApiDefinitionPage(ApiDefinitionPageRequest request, String userId) { public List<ApiDefinitionDTO> getApiDefinitionPage(ApiDefinitionPageRequest request, String userId) {
CustomFieldUtils.setBaseQueryRequestCustomMultipleFields(request, userId); CustomFieldUtils.setBaseQueryRequestCustomMultipleFields(request, userId);
List<ApiDefinitionDTO> list = extApiDefinitionMapper.list(request); List<ApiDefinitionDTO> list = extApiDefinitionMapper.list(request);
if (!CollectionUtils.isEmpty(list)) { processApiDefinitions(list);
processApiDefinitions(list, request.getProjectId());
}
return list; return list;
} }
@ -437,45 +433,42 @@ public class ApiDefinitionService extends MoveNodeService {
} }
} }
private void processApiDefinitions(List<ApiDefinitionDTO> list, String projectId) { private void processApiDefinitions(List<ApiDefinitionDTO> list) {
if (CollectionUtils.isEmpty(list)) {
return;
}
Set<String> userIds = extractUserIds(list); Set<String> userIds = extractUserIds(list);
Map<String, String> userMap = userLoginService.getUserNameMap(new ArrayList<>(userIds)); Map<String, String> userMap = userLoginService.getUserNameMap(new ArrayList<>(userIds));
/* List<String> apiDefinitionIds = list.stream().map(ApiDefinitionDTO::getId).toList();
List<ApiCaseComputeDTO> apiCaseComputeList = extApiDefinitionMapper.selectApiCaseByIdsAndStatusIsNotTrash(apiDefinitionIds, projectId);
Map<String, ApiCaseComputeDTO> resultMap = apiCaseComputeList.stream().collect(Collectors.toMap(ApiCaseComputeDTO::getApiDefinitionId, Function.identity()));
List<ApiDefinitionCustomFieldDTO> customFields = extApiDefinitionCustomFieldMapper.getApiCustomFields(apiDefinitionIds, projectId); List<String> apiDefinitionIds = list.stream().map(ApiDefinitionDTO::getId).toList();
Map<String, List<ApiDefinitionCustomFieldDTO>> customFieldMap = customFields.stream().collect(Collectors.groupingBy(ApiDefinitionCustomFieldDTO::getApiId)); List<ApiTestCase> apiCaseList = extApiDefinitionMapper.selectNotInTrashCaseIdsByApiIds(apiDefinitionIds);
*/ Map<String, List<ApiTestCase>> apiCaseMap = apiCaseList.stream().
collect(Collectors.groupingBy(ApiTestCase::getApiDefinitionId));
List<String> moduleIds = list.stream().map(ApiDefinitionDTO::getModuleId).toList();
List<ApiDefinitionModule> modules = extApiDefinitionModuleMapper.getNameInfoByIds(moduleIds);
Map<String, String> moduleNameMap = modules.stream()
.collect(Collectors.toMap(ApiDefinitionModule::getId, ApiDefinitionModule::getName));
list.forEach(item -> { list.forEach(item -> {
// Convert User IDs to Names // Convert User IDs to Names
item.setCreateUserName(userMap.get(item.getCreateUser())); item.setCreateUserName(userMap.get(item.getCreateUser()));
item.setDeleteUserName(userMap.get(item.getDeleteUser())); item.setDeleteUserName(userMap.get(item.getDeleteUser()));
item.setUpdateUserName(userMap.get(item.getUpdateUser())); item.setUpdateUserName(userMap.get(item.getUpdateUser()));
// Custom Fields
/*item.setCustomFields(customFieldMap.get(item.getId()));
// Calculate API Case Metrics // Calculate API Case Metrics
ApiCaseComputeDTO apiCaseComputeDTO = resultMap.get(item.getId()); List<ApiTestCase> apiTestCases = apiCaseMap.get(item.getId());
if (apiCaseComputeDTO != null) { if (apiTestCases != null) {
item.setCaseTotal(apiCaseComputeDTO.getCaseTotal()); item.setCaseTotal(apiTestCases.size());
item.setCasePassRate(apiCaseComputeDTO.getCasePassRate());
// 状态优先级 未执行未通过误报FAKE_ERROR通过
if ((apiCaseComputeDTO.getError() + apiCaseComputeDTO.getFakeError() + apiCaseComputeDTO.getSuccess()) < apiCaseComputeDTO.getCaseTotal()) {
item.setCaseStatus(ApiReportStatus.PENDING.name());
} else if (apiCaseComputeDTO.getError() > 0) {
item.setCaseStatus(ApiReportStatus.ERROR.name());
} else if (apiCaseComputeDTO.getFakeError() > 0) {
item.setCaseStatus(ApiReportStatus.FAKE_ERROR.name());
} else {
item.setCaseStatus(ApiReportStatus.SUCCESS.name());
}
} else { } else {
item.setCaseTotal(0); item.setCaseTotal(0);
item.setCasePassRate("-"); }
item.setCaseStatus("-");
}*/ if (moduleNameMap.get(item.getModuleId()) == null) {
item.setModuleName(Translator.get("api_unplanned_request"));
} else {
item.setModuleName(moduleNameMap.get(item.getModuleId()));
}
}); });
} }

View File

@ -135,6 +135,9 @@ public class ApiDefinitionControllerTests extends BaseTest {
@Resource @Resource
private ExtApiTestCaseMapper extApiTestCaseMapper; private ExtApiTestCaseMapper extApiTestCaseMapper;
@Resource
private ApiTestCaseMapper apiTestCaseMapper;
@Resource @Resource
private ApiDefinitionModuleMapper apiDefinitionModuleMapper; private ApiDefinitionModuleMapper apiDefinitionModuleMapper;
@ -879,14 +882,35 @@ public class ApiDefinitionControllerTests extends BaseTest {
@Order(9) @Order(9)
@Sql(scripts = {"/dml/init_api_definition.sql"}, config = @SqlConfig(encoding = "utf-8", transactionMode = SqlConfig.TransactionMode.ISOLATED)) @Sql(scripts = {"/dml/init_api_definition.sql"}, config = @SqlConfig(encoding = "utf-8", transactionMode = SqlConfig.TransactionMode.ISOLATED))
public void getPage() throws Exception { public void getPage() throws Exception {
doApiDefinitionPage("All", PAGE); assertPateDate(doApiDefinitionPage("All", PAGE));
doApiDefinitionPage("KEYWORD", PAGE); assertPateDate(doApiDefinitionPage("KEYWORD", PAGE));
//doApiDefinitionPage("FILTER", PAGE); //doApiDefinitionPage("FILTER", PAGE);
doApiDefinitionPage("COMBINE", PAGE); assertPateDate(doApiDefinitionPage("COMBINE", PAGE));
doApiDefinitionPage("DELETED", PAGE); assertPateDate(doApiDefinitionPage("DELETED", PAGE));
} }
private void doApiDefinitionPage(String search, String url) throws Exception { private void assertPateDate(Pager pageData) {
List<ApiDefinitionDTO> apiDefinitions = ApiDataUtils.parseArray(JSON.toJSONString(pageData.getList()), ApiDefinitionDTO.class);
if (CollectionUtils.isNotEmpty(apiDefinitions)) {
ApiDefinitionDTO apiDefinitionDTO = apiDefinitions.get(0);
// 判断用例数是否正确
ApiTestCaseExample example = new ApiTestCaseExample();
example.createCriteria()
.andApiDefinitionIdEqualTo(apiDefinitionDTO.getId())
.andDeletedEqualTo(false);
List<ApiTestCase> apiTestCases = apiTestCaseMapper.selectByExample(example);
Assertions.assertEquals(apiDefinitionDTO.getCaseTotal(), apiTestCases.size());
// 判断模块名是否正确
ApiDefinitionModule apiDefinitionModule = apiDefinitionModuleMapper.selectByPrimaryKey(apiDefinitionDTO.getModuleId());
if (apiDefinitionModule == null) {
Assertions.assertEquals(apiDefinitionDTO.getModuleName(), Translator.get("api_unplanned_request"));
} else {
Assertions.assertEquals(apiDefinitionDTO.getModuleName(), apiDefinitionModule.getName());
}
}
}
private Pager doApiDefinitionPage(String search, String url) throws Exception {
ApiDefinitionPageRequest request = new ApiDefinitionPageRequest(); ApiDefinitionPageRequest request = new ApiDefinitionPageRequest();
request.setProjectId(DEFAULT_PROJECT_ID); request.setProjectId(DEFAULT_PROJECT_ID);
request.setCurrent(1); request.setCurrent(1);
@ -905,19 +929,15 @@ public class ApiDefinitionControllerTests extends BaseTest {
} }
MvcResult mvcResult = this.requestPostWithOkAndReturn(url, request); MvcResult mvcResult = this.requestPostWithOkAndReturn(url, request);
// 获取返回值
String returnData = mvcResult.getResponse().getContentAsString(StandardCharsets.UTF_8);
ResultHolder resultHolder = JSON.parseObject(returnData, ResultHolder.class);
// 返回请求正常 // 返回请求正常
Assertions.assertNotNull(resultHolder); Pager pageData = getResultData(mvcResult, Pager.class);
Pager<?> pageData = JSON.parseObject(JSON.toJSONString(resultHolder.getData()), Pager.class);
// 返回值不为空 // 返回值不为空
Assertions.assertNotNull(pageData); Assertions.assertNotNull(pageData);
// 返回值的页码和当前页码相同 // 返回值的页码和当前页码相同
Assertions.assertEquals(pageData.getCurrent(), request.getCurrent()); Assertions.assertEquals(pageData.getCurrent(), request.getCurrent());
// 返回的数据量不超过规定要返回的数据量相同 // 返回的数据量不超过规定要返回的数据量相同
Assertions.assertTrue(JSON.parseArray(JSON.toJSONString(pageData.getList())).size() <= request.getPageSize()); Assertions.assertTrue(JSON.parseArray(JSON.toJSONString(pageData.getList())).size() <= request.getPageSize());
return pageData;
} }
private void configureAllSearch(ApiDefinitionPageRequest request) { private void configureAllSearch(ApiDefinitionPageRequest request) {