Skip to content

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电商系统测试框架提供了完整的测试解决方案,涵盖了单元测试、集成测试、功能测试、性能测试、安全测试等各个方面。

通过统一的测试框架,可以提高测试效率,降低测试成本,确保测试质量,为系统的质量保证提供有力支撑。

框架具有良好的可扩展性、可维护性和可复用性,能够适应项目的发展和变化。

建议在项目中使用此测试框架,并根据实际需求进行定制和优化,以实现最佳的测试效果。