LiteMall电商系统测试框架¶
1. 测试框架概述¶
1.1 框架目标¶
测试框架的目标是为LiteMall电商系统提供统一的测试解决方案,集成各种测试工具和技术,提供标准化的测试流程,提高测试效率和质量。
1.2 框架范围¶
- 单元测试框架
- 集成测试框架
- 功能测试框架
- 性能测试框架
- 安全测试框架
- 自动化测试框架
1.3 框架特点¶
- 统一性:统一的测试标准和流程
- 可扩展性:支持新测试类型和工具
- 可维护性:便于维护和更新
- 可复用性:提高测试代码复用率
- 自动化:支持自动化测试执行
2. 框架架构¶
2.1 整体架构¶
┌─────────────────────────────────────────────────────────────┐
│ LiteMall测试框架 │
├─────────────────────────────────────────────────────────────┤
│ 测试执行层 (Test Execution Layer) │
├─────────────────────────────────────────────────────────────┤
│ 测试管理层 (Test Management Layer) │
├─────────────────────────────────────────────────────────────┤
│ 测试工具层 (Test Tools Layer) │
├─────────────────────────────────────────────────────────────┤
│ 测试数据层 (Test Data Layer) │
├─────────────────────────────────────────────────────────────┤
│ 测试配置层 (Test Configuration Layer) │
├─────────────────────────────────────────────────────────────┤
│ 基础设施层 (Infrastructure Layer) │
└─────────────────────────────────────────────────────────────┘
2.2 核心组件¶
2.2.1 测试执行引擎¶
- JUnit 5:单元测试执行引擎
- TestNG:测试执行框架
- Maven Surefire:Maven测试插件
- Gradle Test:Gradle测试插件
2.2.2 测试管理工具¶
- TestLink:测试管理平台
- JIRA:缺陷管理工具
- Confluence:测试文档管理
- Git:版本控制工具
2.2.3 测试工具集成¶
- Selenium:Web自动化测试
- RestAssured:API测试
- JMeter:性能测试
- OWASP ZAP:安全测试
3. 单元测试框架¶
3.1 框架配置¶
3.1.1 Maven配置¶
<dependencies>
<!-- JUnit 5 -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.9.2</version>
<scope>test</scope>
</dependency>
<!-- Mockito -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>4.11.0</version>
<scope>test</scope>
</dependency>
<!-- AssertJ -->
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.24.2</version>
<scope>test</scope>
</dependency>
<!-- Spring Boot Test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
3.1.2 测试配置类¶
@ExtendWith(MockitoExtension.class)
@SpringBootTest
@TestPropertySource(locations = "classpath:application-test.properties")
public class BaseTest {
@MockBean
protected UserDao userDao;
@MockBean
protected ProductDao productDao;
@MockBean
protected OrderDao orderDao;
@Autowired
protected TestRestTemplate restTemplate;
@Autowired
protected ObjectMapper objectMapper;
protected String baseUrl = "http://localhost:8080/api";
}
3.2 测试基类¶
3.2.1 服务层测试基类¶
public abstract class ServiceTestBase<T> extends BaseTest {
protected T service;
@BeforeEach
void setUp() {
service = createService();
}
protected abstract T createService();
protected <R> R mockService(Class<R> serviceClass) {
return mock(serviceClass);
}
protected void verifyInteraction(Object mock, VerificationMode mode) {
verify(mock, mode);
}
}
3.2.2 控制器测试基类¶
@WebMvcTest
public abstract class ControllerTestBase<T> extends BaseTest {
@Autowired
protected MockMvc mockMvc;
@MockBean
protected T service;
protected ResultActions performRequest(String method, String url, Object requestBody) throws Exception {
MockHttpServletRequestBuilder builder;
switch (method.toUpperCase()) {
case "GET":
builder = MockMvcRequestBuilders.get(url);
break;
case "POST":
builder = MockMvcRequestBuilders.post(url)
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(requestBody));
break;
case "PUT":
builder = MockMvcRequestBuilders.put(url)
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(requestBody));
break;
case "DELETE":
builder = MockMvcRequestBuilders.delete(url);
break;
default:
throw new IllegalArgumentException("Unsupported HTTP method: " + method);
}
return mockMvc.perform(builder);
}
}
4. 集成测试框架¶
4.1 测试配置¶
4.1.1 测试环境配置¶
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@TestPropertySource(locations = "classpath:application-integration-test.properties")
@Sql(scripts = "/test-data.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
@Sql(scripts = "/cleanup.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
public class IntegrationTestBase {
@Autowired
protected TestRestTemplate restTemplate;
@Autowired
protected TestEntityManager entityManager;
@LocalServerPort
protected int port;
protected String getBaseUrl() {
return "http://localhost:" + port + "/api";
}
}
4.1.2 数据库测试配置¶
@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@TestPropertySource(locations = "classpath:application-test.properties")
public class DatabaseTestBase {
@Autowired
protected TestEntityManager entityManager;
protected <T> T persistAndFlush(T entity) {
return entityManager.persistAndFlush(entity);
}
protected <T> T persist(T entity) {
return entityManager.persist(entity);
}
protected void flush() {
entityManager.flush();
}
protected void clear() {
entityManager.clear();
}
}
4.2 测试工具类¶
4.2.1 测试数据工具¶
@Component
public class TestDataUtils {
public User createTestUser() {
User user = new User();
user.setPhone("13800138000");
user.setPassword("123456");
user.setName("测试用户");
user.setEmail("test@example.com");
return user;
}
public Product createTestProduct() {
Product product = new Product();
product.setName("iPhone 15");
product.setPrice(5999.00);
product.setStock(100);
product.setDescription("最新款iPhone");
return product;
}
public Order createTestOrder() {
Order order = new Order();
order.setUserId("user001");
order.setTotalAmount(5999.00);
order.setStatus(OrderStatus.PENDING);
order.setShippingAddress("北京市朝阳区xxx街道xxx号");
return order;
}
}
4.2.2 测试断言工具¶
public class TestAssertions {
public static void assertUserEquals(User expected, User actual) {
assertThat(actual.getPhone()).isEqualTo(expected.getPhone());
assertThat(actual.getName()).isEqualTo(expected.getName());
assertThat(actual.getEmail()).isEqualTo(expected.getEmail());
}
public static void assertProductEquals(Product expected, Product actual) {
assertThat(actual.getName()).isEqualTo(expected.getName());
assertThat(actual.getPrice()).isEqualTo(expected.getPrice());
assertThat(actual.getStock()).isEqualTo(expected.getStock());
}
public static void assertOrderEquals(Order expected, Order actual) {
assertThat(actual.getUserId()).isEqualTo(expected.getUserId());
assertThat(actual.getTotalAmount()).isEqualTo(expected.getTotalAmount());
assertThat(actual.getStatus()).isEqualTo(expected.getStatus());
}
}
5. 功能测试框架¶
5.1 Web自动化测试框架¶
5.1.1 Selenium配置¶
@Configuration
public class SeleniumConfig {
@Bean
@Scope("prototype")
public WebDriver webDriver() {
WebDriverManager.chromedriver().setup();
ChromeOptions options = new ChromeOptions();
options.addArguments("--headless");
options.addArguments("--no-sandbox");
options.addArguments("--disable-dev-shm-usage");
return new ChromeDriver(options);
}
@Bean
public WebDriverWait webDriverWait(WebDriver webDriver) {
return new WebDriverWait(webDriver, Duration.ofSeconds(10));
}
}
5.1.2 页面对象基类¶
public abstract class BasePage {
protected WebDriver driver;
protected WebDriverWait wait;
public BasePage(WebDriver driver) {
this.driver = driver;
this.wait = new WebDriverWait(driver, Duration.ofSeconds(10));
PageFactory.initElements(driver, this);
}
protected void waitForElementToBeClickable(WebElement element) {
wait.until(ExpectedConditions.elementToBeClickable(element));
}
protected void waitForElementToBeVisible(WebElement element) {
wait.until(ExpectedConditions.visibilityOf(element));
}
protected void clickElement(WebElement element) {
waitForElementToBeClickable(element);
element.click();
}
protected void sendKeysToElement(WebElement element, String text) {
waitForElementToBeVisible(element);
element.clear();
element.sendKeys(text);
}
}
5.2 API测试框架¶
5.2.1 RestAssured配置¶
@Configuration
public class RestAssuredConfig {
@PostConstruct
public void configureRestAssured() {
RestAssured.baseURI = "http://localhost:8080";
RestAssured.basePath = "/api";
RestAssured.enableLoggingOfRequestAndResponseIfValidationFails();
}
}
5.2.2 API测试基类¶
public abstract class ApiTestBase {
protected RequestSpecification getRequestSpec() {
return RestAssured.given()
.contentType(ContentType.JSON)
.log().all();
}
protected Response get(String endpoint) {
return getRequestSpec().get(endpoint);
}
protected Response post(String endpoint, Object body) {
return getRequestSpec().body(body).post(endpoint);
}
protected Response put(String endpoint, Object body) {
return getRequestSpec().body(body).put(endpoint);
}
protected Response delete(String endpoint) {
return getRequestSpec().delete(endpoint);
}
}
6. 性能测试框架¶
6.1 JMeter配置¶
6.1.1 JMeter测试计划¶
<?xml version="1.0" encoding="UTF-8"?>
<jmeterTestPlan version="1.2">
<hashTree>
<TestPlan testname="LiteMall Performance Test">
<elementProp name="TestPlan.arguments" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables">
<collectionProp name="Arguments.arguments"/>
</elementProp>
<stringProp name="TestPlan.user_define_classpath"></stringProp>
</TestPlan>
<hashTree>
<ThreadGroup testname="User Load Test">
<stringProp name="ThreadGroup.num_threads">100</stringProp>
<stringProp name="ThreadGroup.ramp_time">60</stringProp>
<stringProp name="ThreadGroup.duration">300</stringProp>
</ThreadGroup>
</hashTree>
</hashTree>
</jmeterTestPlan>
6.1.2 性能测试工具类¶
@Component
public class PerformanceTestUtils {
public void executeLoadTest(String testPlanPath) {
try {
JMeterUtils.setJMeterHome("path/to/jmeter");
JMeterUtils.loadJMeterProperties("jmeter.properties");
JMeterUtils.initLocale();
HashTree testPlanTree = SaveService.loadTree(new File(testPlanPath));
StandardJMeterEngine jmeter = new StandardJMeterEngine();
jmeter.configure(testPlanTree);
jmeter.run();
} catch (Exception e) {
throw new RuntimeException("Performance test execution failed", e);
}
}
}
7. 安全测试框架¶
7.1 OWASP ZAP集成¶
7.1.1 ZAP配置¶
@Configuration
public class ZapConfig {
@Bean
public ZapClient zapClient() {
return new ZapClient("localhost", 8080);
}
@Bean
public SecurityTestRunner securityTestRunner(ZapClient zapClient) {
return new SecurityTestRunner(zapClient);
}
}
7.1.2 安全测试工具类¶
@Component
public class SecurityTestUtils {
@Autowired
private ZapClient zapClient;
public void runSecurityScan(String targetUrl) {
try {
zapClient.spider.scan(targetUrl);
zapClient.ascan.scan(targetUrl);
// 等待扫描完成
while (zapClient.spider.status() < 100 || zapClient.ascan.status() < 100) {
Thread.sleep(1000);
}
} catch (Exception e) {
throw new RuntimeException("Security scan failed", e);
}
}
public List<Alert> getSecurityAlerts() {
try {
return zapClient.getAlerts();
} catch (Exception e) {
throw new RuntimeException("Failed to get security alerts", e);
}
}
}
8. 测试数据管理¶
8.1 测试数据工厂¶
8.1.1 数据工厂接口¶
public interface TestDataFactory<T> {
T create();
T createWithDefaults();
T createWithCustomValues(Map<String, Object> values);
List<T> createMultiple(int count);
}
8.1.2 用户数据工厂¶
@Component
public class UserDataFactory implements TestDataFactory<User> {
private final Random random = new Random();
@Override
public User create() {
User user = new User();
user.setPhone(generatePhone());
user.setPassword("123456");
user.setName("测试用户" + random.nextInt(1000));
user.setEmail(generateEmail());
return user;
}
@Override
public User createWithDefaults() {
User user = new User();
user.setPhone("13800138000");
user.setPassword("123456");
user.setName("默认用户");
user.setEmail("default@example.com");
return user;
}
@Override
public User createWithCustomValues(Map<String, Object> values) {
User user = create();
values.forEach((key, value) -> {
switch (key) {
case "phone":
user.setPhone((String) value);
break;
case "name":
user.setName((String) value);
break;
case "email":
user.setEmail((String) value);
break;
}
});
return user;
}
@Override
public List<User> createMultiple(int count) {
return IntStream.range(0, count)
.mapToObj(i -> create())
.collect(Collectors.toList());
}
private String generatePhone() {
return "138" + String.format("%08d", random.nextInt(100000000));
}
private String generateEmail() {
return "test" + random.nextInt(1000) + "@example.com";
}
}
8.2 测试数据清理¶
8.2.1 数据清理工具¶
@Component
public class TestDataCleaner {
@Autowired
private UserDao userDao;
@Autowired
private ProductDao productDao;
@Autowired
private OrderDao orderDao;
@Transactional
public void cleanAllData() {
orderDao.deleteAll();
productDao.deleteAll();
userDao.deleteAll();
}
@Transactional
public void cleanUserData() {
userDao.deleteAll();
}
@Transactional
public void cleanProductData() {
productDao.deleteAll();
}
@Transactional
public void cleanOrderData() {
orderDao.deleteAll();
}
}
9. 测试报告¶
9.1 报告生成器¶
9.1.1 测试报告配置¶
@Configuration
public class TestReportConfig {
@Bean
public ExtentReports extentReports() {
ExtentSparkReporter sparkReporter = new ExtentSparkReporter("target/reports/test-report.html");
sparkReporter.config().setDocumentTitle("LiteMall Test Report");
sparkReporter.config().setReportName("LiteMall Test Report");
sparkReporter.config().setTheme(Theme.STANDARD);
ExtentReports extent = new ExtentReports();
extent.attachReporter(sparkReporter);
extent.setSystemInfo("Application", "LiteMall");
extent.setSystemInfo("Environment", "Test");
extent.setSystemInfo("User", System.getProperty("user.name"));
return extent;
}
}
9.1.2 报告管理器¶
@Component
public class TestReportManager {
@Autowired
private ExtentReports extentReports;
private ThreadLocal<ExtentTest> testThreadLocal = new ThreadLocal<>();
public void createTest(String testName, String description) {
ExtentTest test = extentReports.createTest(testName, description);
testThreadLocal.set(test);
}
public void logInfo(String message) {
testThreadLocal.get().info(message);
}
public void logPass(String message) {
testThreadLocal.get().pass(message);
}
public void logFail(String message) {
testThreadLocal.get().fail(message);
}
public void logSkip(String message) {
testThreadLocal.get().skip(message);
}
public void addScreenshot(String screenshotPath) {
testThreadLocal.get().addScreenCaptureFromPath(screenshotPath);
}
public void flushReport() {
extentReports.flush();
}
}
10. 持续集成¶
10.1 CI/CD配置¶
10.1.1 Jenkins Pipeline¶
pipeline {
agent any
stages {
stage('Checkout') {
steps {
checkout scm
}
}
stage('Build') {
steps {
sh 'mvn clean compile'
}
}
stage('Unit Test') {
steps {
sh 'mvn test'
}
post {
always {
publishTestResults testResultsPattern: 'target/surefire-reports/*.xml'
publishHTML([
allowMissing: false,
alwaysLinkToLastBuild: true,
keepAll: true,
reportDir: 'target/site/jacoco',
reportFiles: 'index.html',
reportName: 'Coverage Report'
])
}
}
}
stage('Integration Test') {
steps {
sh 'mvn verify -P integration-test'
}
}
stage('Performance Test') {
steps {
sh 'mvn test -P performance-test'
}
}
stage('Security Test') {
steps {
sh 'mvn test -P security-test'
}
}
}
post {
always {
publishHTML([
allowMissing: false,
alwaysLinkToLastBuild: true,
keepAll: true,
reportDir: 'target/reports',
reportFiles: 'test-report.html',
reportName: 'Test Report'
])
}
}
}
10.2 质量门禁¶
10.2.1 质量门禁配置¶
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.7</version>
<configuration>
<rules>
<rule>
<element>CLASS</element>
<limits>
<limit>
<counter>LINE</counter>
<value>COVEREDRATIO</value>
<minimum>0.80</minimum>
</limit>
</limits>
</rule>
</rules>
</configuration>
</plugin>
11. 最佳实践¶
11.1 框架使用规范¶
11.1.1 测试命名规范¶
- 测试类名:
被测试类名 + Test - 测试方法名:
test + 被测试方法名 + 测试场景 - 测试包名:
com.litemall.test.模块名
11.1.2 测试结构规范¶
- 使用AAA模式(Arrange-Act-Assert)
- 保持测试方法简洁
- 使用有意义的断言
- 避免测试间相互依赖
11.2 框架维护规范¶
11.2.1 版本管理¶
- 定期更新依赖版本
- 保持向后兼容性
- 记录版本变更日志
- 提供升级指南
11.2.2 文档维护¶
- 保持文档更新
- 提供使用示例
- 记录最佳实践
- 提供故障排除指南
12. 总结¶
LiteMall电商系统测试框架提供了完整的测试解决方案,涵盖了单元测试、集成测试、功能测试、性能测试、安全测试等各个方面。
通过统一的测试框架,可以提高测试效率,降低测试成本,确保测试质量,为系统的质量保证提供有力支撑。
框架具有良好的可扩展性、可维护性和可复用性,能够适应项目的发展和变化。
建议在项目中使用此测试框架,并根据实际需求进行定制和优化,以实现最佳的测试效果。