프로젝트의 요구사항을 고려했을 때, 효율적인 테스트 도구 시스템을 구축하기 위한 데이터베이스 설계 및 주요 로직 단계를 Java, Spring, iBatis, MariaDB 환경에 맞춰 상세히 정리해 드릴게요.
1. 데이터베이스 설계 (MariaDB)
테스트 도구의 다양한 파라미터와 계층 구조를 효율적으로 관리하기 위해 다음 테이블들을 설계할 수 있습니다.
1.1 TEST_PLAN
테이블
- 설명: 최상위 테스트 계획 정보를 저장합니다.
컬럼:
PLAN_ID
(BIGINT PK, AUTO\_INCREMENT): 테스트 계획 IDPLAN_NAME
(VARCHAR(255) NOT NULL): 테스트 계획 이름DESCRIPTION
(TEXT): 테스트 계획 설명CREATED_AT
(TIMESTAMP DEFAULT CURRENT\_TIMESTAMP): 생성일시UPDATED_AT
(TIMESTAMP DEFAULT CURRENT\_TIMESTAMP ON UPDATE CURRENT\_TIMESTAMP): 수정일시
1.2 TEST_SCENARIO
테이블
- 설명: 각 테스트 계획에 속하는 시나리오 정보를 저장합니다.
컬럼:
SCENARIO_ID
(BIGINT PK, AUTO\_INCREMENT): 테스트 시나리오 IDPLAN_ID
(BIGINT FK): 소속 테스트 계획 ID (TEST_PLAN.PLAN_ID
참조)SCENARIO_NAME
(VARCHAR(255) NOT NULL): 테스트 시나리오 이름DESCRIPTION
(TEXT): 테스트 시나리오 설명CREATED_AT
(TIMESTAMP DEFAULT CURRENT\_TIMESTAMP): 생성일시UPDATED_AT
(TIMESTAMP DEFAULT CURRENT\_TIMESTAMP ON UPDATE CURRENT\_TIMESTAMP): 수정일시
1.3 TEST_CASE
테이블
- 설명: 각 시나리오에 속하는 테스트 케이스 정보를 저장합니다.
컬럼:
CASE_ID
(BIGINT PK, AUTO\_INCREMENT): 테스트 케이스 IDSCENARIO_ID
(BIGINT FK): 소속 테스트 시나리오 ID (TEST_SCENARIO.SCENARIO_ID
참조)CASE_NAME
(VARCHAR(255) NOT NULL): 테스트 케이스 이름DESCRIPTION
(TEXT): 테스트 케이스 설명CREATED_AT
(TIMESTAMP DEFAULT CURRENT\_TIMESTAMP): 생성일시UPDATED_AT
(TIMESTAMP DEFAULT CURRENT\_TIMESTAMP ON UPDATE CURRENT\_TIMESTAMP): 수정일시
1.4 TEST_ACTIVITY
테이블
- 설명: 각 테스트 케이스에 속하는 개별 테스트 활동(SQL 템플릿 포함) 정보를 저장합니다.
컬럼:
ACTIVITY_ID
(BIGINT PK, AUTO\_INCREMENT): 테스트 활동 IDCASE_ID
(BIGINT FK): 소속 테스트 케이스 ID (TEST_CASE.CASE_ID
참조)ACTIVITY_NAME
(VARCHAR(255) NOT NULL): 테스트 활동 이름SQL_TEMPLATE
(LONGTEXT NOT NULL): 실행될 SQL 템플릿 (파라미터 포함)CREATED_AT
(TIMESTAMP DEFAULT CURRENT\_TIMESTAMP): 생성일시UPDATED_AT
(TIMESTAMP DEFAULT CURRENT\_TIMESTAMP ON UPDATE CURRENT\_TIMESTAMP): 수정일시
1.5 PARAMETER_DEFINITION
테이블
- 설명: 각 계층(Plan, Scenario, Case, Activity)에서 정의된 파라미터 정보를 저장합니다.
컬럼:
PARAM_DEF_ID
(BIGINT PK, AUTO\_INCREMENT): 파라미터 정의 IDREFER_ID
(BIGINT NOT NULL): 참조하는 엔티티 ID (Plan ID, Scenario ID, Case ID, Activity ID)REFER_TYPE
(VARCHAR(20) NOT NULL): 참조하는 엔티티 타입 (PLAN
, SCENARIO
, CASE
, ACTIVITY
)PARAM_NAME
(VARCHAR(100) NOT NULL): 파라미터 이름 (예: testId
, ADD_CONDITION_1
, userId
)PARAM_TYPE
(VARCHAR(50) NOT NULL): 파라미터 타입 (SYSTEM
, CONDITIONAL
, STATIC
, DYNAMIC
, SQL_QUERY
)PARAM_VALUE
(TEXT): 파라미터 값 (고정값, SQL 쿼리, 동적 SQL 등. 타입에 따라 의미 다름)DESCRIPTION
(TEXT): 파라미터 설명CREATED_AT
(TIMESTAMP DEFAULT CURRENT\_TIMESTAMP): 생성일시UPDATED_AT
(TIMESTAMP DEFAULT CURRENT\_TIMESTAMP ON UPDATE CURRENT\_TIMESTAMP): 수정일시
1.6 TEST_EXECUTION_RESULT
테이블
- 설명: 테스트 실행 결과 및 실행된 SQL, 최종 파라미터 값을 저장합니다.
컬럼:
EXEC_ID
(BIGINT PK, AUTO\_INCREMENT): 실행 결과 IDACTIVITY_ID
(BIGINT FK): 실행된 테스트 활동 ID (TEST_ACTIVITY.ACTIVITY_ID
참조)EXECUTED_SQL
(LONGTEXT NOT NULL): 최종 실행된 SQL 문장PARAMETER_VALUES
(JSON): 실행 시 사용된 최종 파라미터 값들 (JSON 형태로 저장하여 유연성 확보)EXECUTION_STATUS
(VARCHAR(50) NOT NULL): 실행 상태 (SUCCESS, FAILED 등)RESULT_DATA
(LONGTEXT): SQL 실행 결과 데이터 (필요시 저장)ERROR_MESSAGE
(TEXT): 에러 발생 시 메시지EXECUTED_AT
(TIMESTAMP DEFAULT CURRENT\_TIMESTAMP): 실행일시
2. 주요 로직 단계별 정리 (Java, Spring, iBatis)
Spring Boot와 MyBatis(iBatis의 후속 프레임워크)를 기반으로 주요 로직 단계를 설명합니다.
2.1. 테스트 계획 수립 및 파라미터 정의 (SQL 템플릿 및 파라미터 정의)
개요:
사용자가 웹 UI를 통해 테스트 계획, 시나리오, 케이스, 액티비티를 정의하고, 각 단계에서 필요한 파라미터들을 설정합니다. 이 정보들은 위에서 설계한 DB 테이블에 저장됩니다.
주요 컴포넌트:
- Spring REST Controller: 클라이언트의 요청을 받아 테스트 계획, 시나리오, 케이스, 액티비티 및 파라미터 정의를 처리합니다.
- Service Layer: 비즈니스 로직을 담당하며, DTO를 통해 데이터를 주고받습니다.
- MyBatis Mapper: DB와 상호작용하여 데이터를 저장하고 조회합니다.
저장 예시 (MyBatis Mapper XML):
<insert id="insertTestPlan" useGeneratedKeys="true" keyProperty="planId">
INSERT INTO TEST_PLAN (PLAN_NAME, DESCRIPTION)
VALUES (#{planName}, #{description})
</insert>
<insert id="insertParameterDefinition">
INSERT INTO PARAMETER_DEFINITION (REFER_ID, REFER_TYPE, PARAM_NAME, PARAM_TYPE, PARAM_VALUE, DESCRIPTION)
VALUES (#{referId}, #{referType}, #{paramName}, #{paramType}, #{paramValue}, #{description})
</insert>
로직 흐름:
- 사용자가 UI를 통해 테스트 계획 생성 및 이름, 설명 입력.
TestPlanController
가 요청을 받아 TestPlanService
호출.TestPlanService
는 TestPlanMapper
를 통해 TEST_PLAN
테이블에 데이터 삽입.- 유사한 방식으로
TEST_SCENARIO
, TEST_CASE
, TEST_ACTIVITY
및 각 단계의 PARAMETER_DEFINITION
데이터를 저장. 특히 SQL_TEMPLATE
은 TEST_ACTIVITY
테이블에 그대로 저장됩니다.
2.2. 테스트 실행 데이터 생성: 계획 기반 실행 가능한 테스트 세트 생성
개요:
사용자가 특정 테스트 계획을 선택하여 실행을 요청하면, 해당 계획에 속한 모든 시나리오, 케이스, 활동들을 조회하여 실행 가능한 테스트 세트를 구성합니다. 이 단계에서는 실제 SQL 실행은 하지 않고, 실행 준비를 위한 데이터를 로드합니다.
주요 컴포넌트:
- Spring REST Controller: 테스트 실행 요청을 받습니다.
- Service Layer: 실행할 테스트 세트를 구성하는 핵심 로직을 포함합니다.
- MyBatis Mapper: 계층별 정보와 파라미터 정의를 조회합니다.
조회 예시 (MyBatis Mapper XML):
<resultMap id="activityWithParams" type="com.example.testtool.model.TestActivity">
<id property="activityId" column="ACTIVITY_ID"/>
<result property="activityName" column="ACTIVITY_NAME"/>
<result property="sqlTemplate" column="SQL_TEMPLATE"/>
<collection property="parameters" ofType="com.example.testtool.model.ParameterDefinition">
<id property="paramDefId" column="PARAM_DEF_ID"/>
<result property="paramName" column="PARAM_NAME"/>
<result property="paramType" column="PARAM_TYPE"/>
<result property="paramValue" column="PARAM_VALUE"/>
</collection>
</resultMap>
<select id="getActivitiesWithParametersByPlanId" resultMap="activityWithParams">
SELECT
ta.ACTIVITY_ID, ta.ACTIVITY_NAME, ta.SQL_TEMPLATE,
pd.PARAM_DEF_ID, pd.PARAM_NAME, pd.PARAM_TYPE, pd.PARAM_VALUE
FROM TEST_ACTIVITY ta
JOIN TEST_CASE tc ON ta.CASE_ID = tc.CASE_ID
JOIN TEST_SCENARIO ts ON tc.SCENARIO_ID = ts.SCENARIO_ID
JOIN TEST_PLAN tp ON ts.PLAN_ID = tp.PLAN_ID
LEFT JOIN PARAMETER_DEFINITION pd ON (pd.REFER_ID = ta.ACTIVITY_ID AND pd.REFER_TYPE = 'ACTIVITY')
OR (pd.REFER_ID = tc.CASE_ID AND pd.REFER_TYPE = 'CASE')
OR (pd.REFER_ID = ts.SCENARIO_ID AND pd.REFER_TYPE = 'SCENARIO')
OR (pd.REFER_ID = tp.PLAN_ID AND pd.REFER_TYPE = 'PLAN')
WHERE tp.PLAN_ID = #{planId}
ORDER BY ts.SCENARIO_ID, tc.CASE_ID, ta.ACTIVITY_ID, pd.REFER_TYPE DESC, pd.PARAM_NAME
</select>
로직 흐름:
- 사용자가 특정
PLAN_ID
로 테스트 실행 요청. TestExecutionService
는 getActivitiesWithParametersByPlanId
같은 매퍼를 호출하여 해당 계획에 속한 모든 Activity와 각 계층(Plan, Scenario, Case, Activity)에 정의된 모든 파라미터를 조회합니다.- 조회된 데이터를 바탕으로
ExecutionJob
또는 TestExecution
객체 리스트를 생성합니다. 각 ExecutionJob
은 하나의 TestActivity
와 해당 Activity
에 적용될 가능성이 있는 모든 상위/자신 계층의 파라미터 정보를 포함합니다.
2.3. 파라미터 바인딩: 계층적 파라미터 우선순위 적용
개요:
이 단계는 가장 중요하며, 조회된 SQL 템플릿에 최종적으로 적용될 파라미터 값을 결정합니다. 우선순위는 Test Activity (Level 1) \> Test Case (Level 2) \> Test Scenario (Level 3) \> Test Plan (Level 4) 순으로 적용됩니다.
주요 컴포넌트:
- ParameterResolver Service/Utility: 파라미터 우선순위를 처리하고 최종 파라미터 맵을 생성합니다.
- JdbcTemplate (또는 MyBatis의 동적 SQL 기능): SQL 템플릿에 파라미터를 바인딩합니다.
로직 흐름 (ParameterResolver
의 역할):
public Map<String, Object> resolveParameters(Long planId, Long scenarioId, Long caseId, Long activityId) {
Map<String, Object> finalParameters = new HashMap<>();
// 1. Level 4: Test Plan 파라미터 로드 및 적용
loadAndApplyParameters(finalParameters, planId, "PLAN");
// 2. Level 3: Test Scenario 파라미터 로드 및 적용 (동일 이름 시 덮어쓰기)
loadAndApplyParameters(finalParameters, scenarioId, "SCENARIO");
// 3. Level 2: Test Case 파라미터 로드 및 적용 (동일 이름 시 덮어쓰기)
loadAndApplyParameters(finalParameters, caseId, "CASE");
// 4. Level 1: Test Activity 파라미터 로드 및 적용 (동일 이름 시 덮어쓰기)
loadAndApplyParameters(finalParameters, activityId, "ACTIVITY");
// 5. 시스템 파라미터 추가 (항상 최신값으로 적용)
finalParameters.put("testId", "TEST_" + System.currentTimeMillis());
finalParameters.put("scenarioId", "SCENARIO_" + System.currentTimeMillis());
// 6. 런타임 계산 파라미터 (Dynamic Parameters) 처리
// PARAM_TYPE이 'DYNAMIC'인 파라미터는 PARAM_VALUE에 정의된 SQL을 실행하여 결과값을 finalParameters에 추가
processDynamicParameters(finalParameters);
// 7. SQL 쿼리 결과값 파라미터 (SQL Query Parameters) 처리
// PARAM_TYPE이 'SQL_QUERY'인 파라미터는 PARAM_VALUE에 정의된 SQL을 실행하여 결과값을 finalParameters에 추가
// 이는 SQL 템플릿 치환 이전에 처리되어야 함.
processSqlQueryParameters(finalParameters);
return finalParameters;
}
private void loadAndApplyParameters(Map<String, Object> params, Long referId, String referType) {
if (referId == null) return;
List<ParameterDefinition> definitions = parameterDefinitionMapper.findByReferIdAndType(referId, referType);
for (ParameterDefinition def : definitions) {
// SYSTEM, CONDITIONAL, STATIC 파라미터는 여기서 직접 적용
if (def.getParamType().equals("SYSTEM") || def.getParamType().equals("STATIC") || def.getParamType().equals("CONDITIONAL")) {
params.put(def.getParamName(), def.getParamValue());
}
// DYNAMIC, SQL_QUERY 타입은 나중에 별도 처리
}
}
private void processDynamicParameters(Map<String, Object> params) {
// PARAMETER_DEFINITION 테이블에서 'DYNAMIC' 타입의 파라미터를 찾아
// PARAM_VALUE에 있는 SQL을 실행하여 결과값을 params에 추가
// 이 과정은 DataSource 및 JdbcTemplate 필요
// 예: select sysdate from dual
}
private void processSqlQueryParameters(Map<String, Object> params) {
// PARAMETER_DEFINITION 테이블에서 'SQL_QUERY' 타입의 파라미터를 찾아
// PARAM_VALUE에 있는 SQL을 실행하여 결과값을 params에 추가
// 이 결과는 해당 파라미터가 사용될 SQL 템플릿에 직접 치환될 예정
}
파라미터 처리 순서 (최종 SQL 생성 전):
- 시스템 파라미터 치환 (
$paramName
): $testId
, $scenarioId
등 시스템에서 사전에 정의된 파라미터들을 ParameterResolver
에서 생성된 최종 파라미터 맵의 값으로 치환합니다. - SQL 쿼리 결과값 파라미터 (
SQL Query Parameters
) 처리: SQL Query Parameters
타입의 파라미터(PARAM_TYPE = 'SQL_QUERY'
)는 PARAM_VALUE
에 정의된 SQL을 직접 실행하여 그 결과값으로 SQL 템플릿 내의 해당 파라미터 이름을 대체합니다. 이는 SQL 문맥에 따라 다르게 처리될 수 있으므로, SQL 템플릿 치환 전에 미리 처리되어야 합니다. - 조건부 SQL 블록 처리 (
#{conditionalSql}
): #{ADD_CONDITION_1}
과 같이 조건부로 추가되는 SQL 블록을 처리합니다. ParameterResolver
에서 결정된 최종 파라미터 맵에 ADD_CONDITION_1
이라는 키가 존재하고 값이 비어있지 않다면 해당 값을 SQL에 삽입합니다. 값이 없거나 비어있으면 해당 블록을 제거합니다. - Add-On 파라미터 처리 (WHERE 절 추가): 이 부분은
CONDITIONAL
파라미터와 유사하게 처리될 수 있습니다. PARAM_TYPE = 'CONDITIONAL'
로 정의된 파라미터들이 finalParameters
맵에 존재하고 그 값이 유효하다면, SQL 템플릿의 WHERE 1=1
뒤에 해당 조건절을 추가합니다.
2.4. SQL 실행: 동적 SQL 생성 및 데이터베이스 실행
개요:
파라미터 바인딩이 완료된 최종 SQL을 MariaDB에 실행하고 그 결과를 저장합니다.
주요 컴포넌트:
- Spring
JdbcTemplate
: 동적으로 생성된 SQL을 실행하기에 적합합니다. - MyBatis Dynamic SQL: XML 또는 어노테이션 기반으로 동적 SQL을 생성하는 데 사용할 수 있습니다. 여기서는
JdbcTemplate
사용이 더 유연합니다. - Transaction Management: SQL 실행 시 트랜잭션 관리가 필요합니다.
로직 흐름:
@Service
public class SqlExecutorService {
private final JdbcTemplate jdbcTemplate;
private final TestExecutionResultMapper resultMapper;
public SqlExecutorService(JdbcTemplate jdbcTemplate, TestExecutionResultMapper resultMapper) {
this.jdbcTemplate = jdbcTemplate;
this.resultMapper = resultMapper;
}
@Transactional
public void executeSqlAndSaveResult(Long activityId, String processedSql, Map<String, Object> finalParameters) {
String executionStatus = "FAILED";
String errorMessage = null;
String resultData = null;
try {
// SELECT 문인 경우 쿼리 실행 및 결과 저장
if (processedSql.trim().toUpperCase().startsWith("SELECT")) {
List<Map<String, Object>> rows = jdbcTemplate.queryForList(processedSql);
resultData = new ObjectMapper().writeValueAsString(rows); // JSON 형태로 저장
}
// INSERT, UPDATE, DELETE 등 DML 문인 경우 업데이트 갯수 저장
else {
int affectedRows = jdbcTemplate.update(processedSql);
resultData = "Affected Rows: " + affectedRows;
}
executionStatus = "SUCCESS";
} catch (Exception e) {
errorMessage = e.getMessage();
// 에러 로깅
} finally {
// 실행 결과 저장
TestExecutionResult result = new TestExecutionResult();
result.setActivityId(activityId);
result.setExecutedSql(processedSql);
result.setParameterValues(new ObjectMapper().writeValueAsString(finalParameters)); // JSON 문자열로 변환
result.setExecutionStatus(executionStatus);
result.setResultData(resultData);
result.setErrorMessage(errorMessage);
resultMapper.insertTestExecutionResult(result);
}
}
}
2.5. 결과 검증: SQL 수행 결과 저장
개요:
SQL 실행 후 결과를 TEST_EXECUTION_RESULT
테이블에 저장합니다. 이 결과는 나중에 테스트 리포트 생성 등에 활용될 수 있습니다.
주요 컴포넌트:
- MyBatis Mapper:
TEST_EXECUTION_RESULT
테이블에 데이터를 삽입합니다.
저장 예시 (MyBatis Mapper XML):
<insert id="insertTestExecutionResult" useGeneratedKeys="true" keyProperty="execId">
INSERT INTO TEST_EXECUTION_RESULT (ACTIVITY_ID, EXECUTED_SQL, PARAMETER_VALUES, EXECUTION_STATUS, RESULT_DATA, ERROR_MESSAGE)
VALUES (#{activityId}, #{executedSql}, #{parameterValues}, #{executionStatus}, #{resultData}, #{errorMessage})
</insert>
3. 구현 시 고려사항 및 추가 제안
- 보안: 동적 SQL 실행은 SQL Injection에 매우 취약합니다. 사용자 입력 값은 절대로 직접 SQL에 삽입하지 않도록 철저히 검증하고, 가능하다면 PreparedStatement를 통해 바인딩해야 합니다.
SQL_QUERY
타입의 파라미터나 동적 SQL을 사용할 경우, 최대한 제한적인 권한의 DB 계정을 사용하고, 입력값에 대한 화이트리스트 방식의 검증을 강력히 적용해야 합니다. - 런타임 계산 파라미터 (Dynamic Parameters) 구현:
PARAM_VALUE
에 저장된 SQL을 별도의 JdbcTemplate
으로 실행하여 결과값을 받아와야 합니다. 이때, 해당 SQL 자체도 또 다른 파라미터를 포함할 수 있는지 등 복잡도를 고려해야 합니다. - SQL 쿼리 결과값 파라미터 (SQL Query Parameters) 구현:
PARAM_VALUE
에 정의된 SQL을 실행하고, 그 결과값(단일 값으로 가정)으로 SQL 템플릿 내의 특정 ${paramName}을 대체해야 합니다. 이는 문자열 치환 방식으로 구현될 수 있습니다. - 트랜잭션 관리: 각 테스트 활동의 SQL 실행은 독립적인 트랜잭션으로 관리하거나, 전체 테스트 계획 실행을 하나의 큰 트랜잭션으로 묶을지 전략을 수립해야 합니다.
- 비동기 처리: 대량의 테스트 케이스를 실행할 경우, 각
TestActivity
의 SQL 실행을 비동기로 처리하여 시스템 응답성을 높일 수 있습니다 (예: Spring @Async
, CompletableFuture
). - 에러 핸들링: SQL 실행 중 발생하는 다양한 예외 상황에 대한 견고한 에러 핸들링 로직이 필요합니다.
- 결과 리포팅: 저장된
TEST_EXECUTION_RESULT
데이터를 기반으로 테스트 실행 보고서를 생성하는 기능이 추가되어야 합니다. - UI/UX: 사용자가 파라미터를 쉽게 정의하고, SQL 템플릿을 작성하며, 실행 결과를 직관적으로 확인할 수 있는 웹 UI 개발이 중요합니다.
이 설계와 로직 흐름은 제시된 요구사항을 충족하며, 안정적이고 확장 가능한 테스트 도구 시스템을 구축하는 데 도움이 될 것입니다.