Developer Guide

测试指南

Tagtag Starter 项目的测试指南,包括单元测试、集成测试和 API 测试。

测试是保证软件质量的重要手段,Tagtag Starter 项目采用多层次的测试策略,包括单元测试、集成测试和 API 测试。本文档将详细介绍 Tagtag Starter 项目的测试方法、工具和最佳实践。

1. 测试概述

1.1 测试分层

Tagtag Starter 项目的测试分为以下几个层次:

测试类型测试对象测试范围执行速度依赖关系
单元测试单个类/函数最小单位
集成测试多个模块模块间交互
API 测试REST API接口级
E2E 测试整个应用端到端流程

1.2 测试目标

  • 保证功能正确性:验证代码实现了预期功能
  • 提高代码质量:通过测试发现潜在的问题和缺陷
  • 增强代码可维护性:测试可以作为代码的文档,方便后续维护
  • 支持持续集成:自动化测试是持续集成的基础
  • 减少回归问题:防止修改代码时引入新的问题

2. 后端测试

Tagtag Starter 后端使用 Java 语言开发,采用 JUnit 5 和 Mockito 进行测试。

2.1 单元测试

单元测试是对单个类或函数的测试,验证其在隔离环境下的行为。

2.1.1 测试工具

  • JUnit 5:Java 单元测试框架
  • Mockito:Java 模拟框架,用于模拟依赖对象
  • AssertJ:流畅的断言库,提供更好的断言体验

2.1.2 测试结构

单元测试文件通常与被测试类放在同一包下,位于 src/test/java 目录中。

src
└── main
    └── java
        └── dev
            └── tagtag
                └── module
                    └── system
                        └── service
                            └── impl
                                └── PostServiceImpl.java
└── test
    └── java
        └── dev
            └── tagtag
                └── module
                    └── system
                        └── service
                            └── impl
                                └── PostServiceImplTest.java

2.1.3 测试示例

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
import static org.assertj.core.api.Assertions.assertThat;

@ExtendWith(MockitoExtension.class)
class PostServiceImplTest {

    @Mock
    private PostMapper postMapper;

    @InjectMocks
    private PostServiceImpl postService;

    @Test
    void testPage() {
        // 准备测试数据
        PostQuery query = new PostQuery();
        query.setPage(1);
        query.setSize(10);
        query.setTitle("测试");

        // 模拟分页结果
        Page<PostEntity> page = new Page<>(1, 10);
        List<PostEntity> records = new ArrayList<>();
        records.add(new PostEntity(1L, "测试文章", "内容", 1));
        page.setRecords(records);
        page.setTotal(1);

        // 设置 Mock 行为
        when(postMapper.selectPage(any(Page.class), any(LambdaQueryWrapper.class))).thenReturn(page);

        // 执行测试
        PageResult<PostEntity> result = postService.page(query);

        // 验证结果
        assertThat(result).isNotNull();
        assertThat(result.getTotal()).isEqualTo(1);
        assertThat(result.getRecords()).hasSize(1);
        assertThat(result.getRecords().get(0).getTitle()).isEqualTo("测试文章");
    }
}

2.1.4 测试最佳实践

  • 测试单个方法:每个测试方法只测试一个功能点
  • 使用有意义的测试名称:测试名称应描述测试的功能和预期结果
  • 使用 Mock 隔离依赖:使用 Mockito 模拟外部依赖,避免测试被外部因素影响
  • 测试边界条件:测试空值、边界值、异常情况等
  • 保持测试简洁:测试代码应简洁、清晰,易于理解

2.2 集成测试

集成测试是对多个模块的测试,验证模块间的交互是否正确。

2.2.1 测试工具

  • Spring Boot Test:Spring Boot 集成测试框架
  • TestContainers:提供轻量级的容器化测试环境
  • H2 Database:内存数据库,用于测试数据库操作

2.2.2 测试结构

集成测试文件通常与单元测试放在同一目录中,但需要使用 @SpringBootTest 注解。

2.2.3 测试示例

@SpringBootTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@Transactional
class PostServiceIntegrationTest {

    @Autowired
    private PostService postService;

    @Autowired
    private PostMapper postMapper;

    @Test
    void testSaveAndGetPost() {
        // 准备测试数据
        PostDTO postDTO = new PostDTO();
        postDTO.setTitle("集成测试文章");
        postDTO.setContent("集成测试内容");
        postDTO.setStatus(1);

        // 保存文章
        postService.save(postDTO);

        // 验证文章已保存
        List<PostEntity> posts = postMapper.selectList(null);
        assertThat(posts).hasSize(1);
        assertThat(posts.get(0).getTitle()).isEqualTo("集成测试文章");

        // 分页查询文章
        PostQuery query = new PostQuery();
        query.setPage(1);
        query.setSize(10);
        query.setTitle("集成测试");

        PageResult<PostEntity> result = postService.page(query);
        assertThat(result).isNotNull();
        assertThat(result.getTotal()).isEqualTo(1);
        assertThat(result.getRecords()).hasSize(1);
        assertThat(result.getRecords().get(0).getTitle()).isEqualTo("集成测试文章");
    }
}

2.2.4 测试最佳实践

  • 测试真实的依赖关系:集成测试应使用真实的依赖关系,而不是 Mock
  • 使用内存数据库:使用 H2 等内存数据库,避免影响真实数据库
  • 使用事务管理:使用 @Transactional 注解,确保测试数据不会污染数据库
  • 测试核心业务流程:集成测试应测试核心的业务流程,而不是单个功能点

2.3 API 测试

API 测试是对 REST API 的测试,验证接口的正确性和可靠性。

2.3.1 测试工具

  • Spring Boot Test:提供 TestRestTemplateWebTestClient 用于 API 测试
  • JUnit 5:测试框架
  • AssertJ:断言库

2.3.2 测试示例

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class PostControllerApiTest {

    @Autowired
    private TestRestTemplate restTemplate;

    @Test
    void testPagePosts() {
        // 准备测试数据
        PostDTO postDTO = new PostDTO();
        postDTO.setTitle("API 测试文章");
        postDTO.setContent("API 测试内容");
        postDTO.setStatus(1);

        // 保存文章
        restTemplate.postForEntity("/posts", postDTO, Void.class);

        // 测试分页查询
        ResponseEntity<Result<PageResult<PostDTO>>> response = restTemplate.getForEntity(
                "/posts?page=1&size=10&title=API测试",
                new ParameterizedTypeReference<Result<PageResult<PostDTO>>>() {}
        );

        // 验证结果
        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
        Result<PageResult<PostDTO>> result = response.getBody();
        assertThat(result).isNotNull();
        assertThat(result.isSuccess()).isTrue();
        assertThat(result.getData().getTotal()).isEqualTo(1);
        assertThat(result.getData().getRecords()).hasSize(1);
        assertThat(result.getData().getRecords().get(0).getTitle()).isEqualTo("API 测试文章");
    }
}

2.3.3 测试最佳实践

  • 测试所有 HTTP 方法:测试 GET、POST、PUT、DELETE 等所有 HTTP 方法
  • 测试不同的请求参数:测试不同的查询参数、路径参数和请求体
  • 测试错误情况:测试无效参数、权限不足等错误情况
  • 验证响应格式:验证响应的状态码、响应头和响应体格式
  • 使用 JSON Schema 验证:使用 JSON Schema 验证响应体的结构

3. 前端测试

Tagtag Starter 前端使用 TypeScript 语言开发,采用 Vitest 和 Cypress 进行测试。

3.1 单元测试

前端单元测试是对单个组件或函数的测试,验证其在隔离环境下的行为。

3.1.1 测试工具

  • Vitest:现代化的前端测试框架,基于 Vite
  • Vue Test Utils:Vue 组件测试库
  • jsdom:浏览器环境模拟

3.1.2 测试结构

前端单元测试文件通常与被测试组件放在同一目录中,使用 .test.ts.spec.ts 后缀。

src
└── views
    └── modules
        └── system
            └── post
                ├── index.vue
                ├── FormModal.vue
                └── __tests__
                    ├── index.test.ts
                    └── FormModal.test.ts

3.1.3 测试示例

import { describe, it, expect, vi } from 'vitest';
import { mount } from '@vue/test-utils';
import PostIndex from '../index.vue';
import { useTable } from '@vben/plugins/vxe-table';

// 模拟依赖
vi.mock('@vben/plugins/vxe-table', () => ({
  useTable: vi.fn(() => [
    // 模拟 Grid 组件
    { template: '<div></div>' },
    // 模拟 gridApi
    {
      refresh: vi.fn(),
      getSelectedRows: vi.fn(() => [])
    }
  ])
}));

describe('PostIndex Component', () => {
  it('should render correctly', () => {
    const wrapper = mount(PostIndex);
    expect(wrapper.exists()).toBe(true);
  });
  
  it('should open modal when add button is clicked', async () => {
    const wrapper = mount(PostIndex, {
      global: {
        stubs: {
          Grid: { template: '<div @toolbar-click="$emit(\'toolbar-click\', $event)"></div>' },
          FormModal: { template: '<div></div>' }
        }
      }
    });
    
    // 触发工具栏点击事件
    await wrapper.findComponent({ name: 'Grid' }).vm.$emit('toolbar-click', { code: 'add' });
    
    // 验证模态框已打开
    expect(wrapper.vm.modalVisible).toBe(true);
    expect(wrapper.vm.modalTitle).toBe('新增文章');
  });
});

3.1.4 测试最佳实践

  • 测试组件渲染:验证组件能够正确渲染
  • 测试组件交互:测试组件对用户交互的响应
  • 测试 props 和 emits:验证组件的 props 和 emits 工作正常
  • 模拟外部依赖:使用 vi.mock 模拟外部依赖
  • 测试计算属性和 watch:验证计算属性和 watch 的行为

3.2 集成测试

前端集成测试是对多个组件的测试,验证组件间的交互是否正确。

3.2.1 测试工具

  • Vitest:与单元测试使用相同的测试框架
  • Vue Test Utils:Vue 组件测试库
  • Mock Service Worker (MSW):API 模拟库,用于模拟后端 API

3.2.2 测试示例

import { describe, it, expect, vi, beforeEach } from 'vitest';
import { mount } from '@vue/test-utils';
import { setupServer } from 'msw/node';
import { rest } from 'msw';
import PostIndex from '../index.vue';
import { useTable } from '@vben/plugins/vxe-table';

// 模拟依赖
vi.mock('@vben/plugins/vxe-table', () => ({
  useTable: vi.fn(() => [
    { template: '<div @toolbar-click="$emit(\'toolbar-click\', $event)"></div>' },
    {
      refresh: vi.fn(),
      getSelectedRows: vi.fn(() => [])
    }
  ])
}));

// 设置 MSW 服务器
const server = setupServer(
  rest.get('/api/posts', (req, res, ctx) => {
    return res(
      ctx.status(200),
      ctx.json({
        success: true,
        data: {
          records: [
            { id: 1, title: '测试文章', content: '测试内容', status: 1, createTime: new Date().toISOString() }
          ],
          total: 1
        }
      })
    );
  }),
  rest.post('/api/posts', (req, res, ctx) => {
    return res(ctx.status(200), ctx.json({ success: true }));
  })
);

// 启动服务器
beforeAll(() => server.listen());
// 关闭服务器
afterAll(() => server.close());
// 重置请求处理器
afterEach(() => server.resetHandlers());

describe('PostIndex Integration Test', () => {
  it('should load posts correctly', async () => {
    const wrapper = mount(PostIndex, {
      global: {
        stubs: {
          Grid: { template: '<div></div>' },
          FormModal: { template: '<div></div>' }
        }
      }
    });
    
    // 模拟 gridApi.refresh() 触发数据加载
    const [_, gridApi] = useTable();
    await gridApi.refresh();
    
    // 验证数据已加载
    // 这里可以根据实际实现添加更详细的验证
    expect(gridApi.refresh).toHaveBeenCalled();
  });
});

3.2.3 测试最佳实践

  • 测试组件间交互:验证组件间的通信和数据传递
  • 模拟后端 API:使用 MSW 模拟后端 API,避免依赖真实后端
  • 测试完整的用户流程:测试从用户输入到数据更新的完整流程
  • 验证状态管理:验证 Pinia 状态管理的行为

3.3 E2E 测试

E2E (End-to-End) 测试是对整个应用的测试,验证端到端的用户流程。

3.3.1 测试工具

  • Cypress:现代化的 E2E 测试框架
  • Chrome/Firefox:真实的浏览器环境

3.3.2 测试结构

E2E 测试文件通常放在 cypress/e2e 目录中,使用 .cy.ts 后缀。

3.3.3 测试示例

describe('Post Management', () => {
  beforeEach(() => {
    // 登录系统
    cy.visit('/');
    cy.get('input[name="username"]').type('admin');
    cy.get('input[name="password"]').type('admin123');
    cy.get('button[type="submit"]').click();
    
    // 导航到文章管理页面
    cy.visit('/system/post');
  });
  
  it('should add a new post', () => {
    // 点击新增按钮
    cy.get('button:contains("新增")').click();
    
    // 填写表单
    cy.get('input[placeholder="请输入文章标题"]').type('E2E 测试文章');
    cy.get('textarea[placeholder="请输入文章内容"]').type('E2E 测试内容');
    cy.get('button:contains("保存")').click();
    
    // 验证文章已添加
    cy.get('.ant-table-row').should('contain', 'E2E 测试文章');
  });
  
  it('should edit a post', () => {
    // 点击编辑按钮
    cy.get('.ant-table-row').first().find('a:contains("编辑")').click();
    
    // 修改表单
    cy.get('input[placeholder="请输入文章标题"]').clear().type('修改后的 E2E 测试文章');
    cy.get('button:contains("保存")').click();
    
    // 验证文章已修改
    cy.get('.ant-table-row').should('contain', '修改后的 E2E 测试文章');
  });
  
  it('should delete a post', () => {
    // 点击删除按钮
    cy.get('.ant-table-row').first().find('a:contains("删除")').click();
    
    // 确认删除
    cy.get('.ant-modal-confirm-btns').find('button:contains("确定")').click();
    
    // 验证文章已删除
    cy.get('.ant-table-row').should('not.contain', '修改后的 E2E 测试文章');
  });
});

3.3.4 测试最佳实践

  • 测试核心用户流程:测试用户最常用的核心流程
  • 使用真实数据:使用真实的测试数据,避免使用硬编码的测试数据
  • 保持测试独立:每个测试应该是独立的,不依赖其他测试的结果
  • 使用页面对象模式:将页面操作封装为页面对象,提高测试的可维护性
  • 设置合理的超时时间:根据网络情况和应用性能设置合理的超时时间

3.4 安全测试

安全测试是前端测试的重要组成部分,主要关注 XSS、CSRF 等安全漏洞。

3.4.1 XSS 防护测试

XSS(跨站脚本攻击)是前端最常见的安全漏洞之一。以下是 XSS 防护的测试示例:

// 必须修复:XSS 漏洞
// 不推荐 - 直接渲染用户输入的内容
function renderUserContent(content: string) {
  return `<div>${content}</div>`;
}

// 推荐 - 使用 DOMPurify 进行 HTML 净化
import DOMPurify from 'dompurify';

function renderUserContent(content: string) {
  const cleanContent = DOMPurify.sanitize(content);
  return `<div>${cleanContent}</div>`;
}

// 推荐 - 使用 Vue 的 v-text 指令
<template>
  <div v-text="userContent"></div>
</template>

// 推荐 - 使用 Vue 的插值表达式(自动转义)
<template>
  <div>{{ userContent }}</div>
</template>

// 不推荐 - 使用 v-html 渲染用户输入
<template>
  <div v-html="userContent"></div>
</template>
// XSS 测试用例示例
import { describe, it, expect } from 'vitest';
import { renderUserContent } from './utils';

describe('XSS Protection', () => {
  it('should sanitize malicious scripts', () => {
    const maliciousContent = '<script>alert("XSS")</script>';
    const rendered = renderUserContent(maliciousContent);
    
    // 验证 script 标签被移除
    expect(rendered).not.toContain('<script>');
    expect(rendered).not.toContain('alert("XSS")');
  });
  
  it('should sanitize img onerror', () => {
    const maliciousContent = '<img src="x" onerror="alert(1)">';
    const rendered = renderUserContent(maliciousContent);
    
    // 验证 onerror 事件被移除
    expect(rendered).not.toContain('onerror');
  });
  
  it('should sanitize javascript: protocol', () => {
    const maliciousContent = '<a href="javascript:alert(1)">Click me</a>';
    const rendered = renderUserContent(maliciousContent);
    
    // 验证 javascript: 协议被移除或转义
    expect(rendered).not.toContain('javascript:');
  });
  
  it('should allow safe HTML', () => {
    const safeContent = '<p>Safe <strong>content</strong></p>';
    const rendered = renderUserContent(safeContent);
    
    // 验证安全的 HTML 标签被保留
    expect(rendered).toContain('<p>');
    expect(rendered).toContain('<strong>');
  });
});

3.4.2 CSRF 防护测试

CSRF(跨站请求伪造)是另一个常见的安全漏洞。以下是 CSRF 防护的测试示例:

// 必须修复:CSRF 漏洞
// 不推荐 - 没有 CSRF Token 保护
async function updateProfile(data: any) {
  return axios.post('/api/profile', data);
}

// 推荐 - 使用 CSRF Token
async function updateProfile(data: any) {
  const csrfToken = getCsrfToken();
  return axios.post('/api/profile', data, {
    headers: {
      'X-CSRF-TOKEN': csrfToken
    }
  });
}

// 推荐 - 使用 axios 拦截器自动添加 CSRF Token
axios.interceptors.request.use(config => {
  const csrfToken = getCsrfToken();
  if (csrfToken) {
    config.headers['X-CSRF-TOKEN'] = csrfToken;
  }
  return config;
});
// CSRF 测试用例示例
import { describe, it, expect, vi } from 'vitest';
import { updateProfile } from './api';

describe('CSRF Protection', () => {
  it('should include CSRF token in request headers', async () => {
    const mockAxios = vi.spyOn(axios, 'post').mockResolvedValue({ data: {} });
    
    await updateProfile({ name: 'Test' });
    
    // 验证请求头包含 CSRF Token
    expect(mockAxios).toHaveBeenCalledWith(
      '/api/profile',
      { name: 'Test' },
      expect.objectContaining({
        headers: expect.objectContaining({
          'X-CSRF-TOKEN': expect.any(String)
        })
      })
    );
  });
});

3.4.3 敏感信息泄露测试

敏感信息泄露是前端安全的另一个重要方面。以下是敏感信息防护的测试示例:

// 必须修复:敏感信息泄露
// 不推荐 - 在 localStorage 中存储敏感信息
function saveToken(token: string) {
  localStorage.setItem('authToken', token);
}

// 推荐 - 使用 sessionStorage 或内存存储
function saveToken(token: string) {
  sessionStorage.setItem('authToken', token);
}

// 推荐 - 使用 httpOnly Cookie
// 后端设置 Cookie: Set-Cookie: authToken=xxx; HttpOnly; Secure; SameSite=Strict

// 不推荐 - 在 URL 中传递敏感信息
function redirectToProfile(userId: string) {
  window.location.href = `/profile?userId=${userId}`;
}

// 推荐 - 使用路由参数或状态管理
function redirectToProfile(userId: string) {
  router.push({ name: 'Profile', params: { userId } });
}
// 敏感信息泄露测试用例示例
import { describe, it, expect, vi } from 'vitest';
import { saveToken } from './auth';

describe('Sensitive Information Protection', () => {
  it('should not store token in localStorage', () => {
    const mockLocalStorage = vi.spyOn(Storage.prototype, 'setItem');
    
    saveToken('test-token');
    
    // 验证没有使用 localStorage
    expect(mockLocalStorage).not.toHaveBeenCalled();
  });
  
  it('should store token in sessionStorage', () => {
    const mockSessionStorage = vi.spyOn(sessionStorage, 'setItem');
    
    saveToken('test-token');
    
    // 验证使用了 sessionStorage
    expect(mockSessionStorage).toHaveBeenCalledWith('authToken', 'test-token');
  });
});

3.5 性能测试

性能测试是确保应用响应速度和用户体验的重要手段。

3.5.1 渲染性能测试

// 必须修复:性能问题 - 不必要的重新渲染
// 不推荐
<template>
  <div v-for="item in items" :key="item.id">
    <ExpensiveComponent :data="item" />
  </div>
</template>

// 推荐 - 使用虚拟滚动
<template>
  <VirtualList :items="items" :item-size="100">
    <template #default="{ item }">
      <ExpensiveComponent :data="item" />
    </template>
  </VirtualList>
</template>

// 推荐 - 使用 v-once 静态内容
<template>
  <div v-once>{{ staticContent }}</div>
  <div>{{ dynamicContent }}</div>
</template>
// 渲染性能测试用例示例
import { describe, it, expect } from 'vitest';
import { mount } from '@vue/test-utils';
import PostList from './PostList.vue';

describe('Rendering Performance', () => {
  it('should render large list efficiently', async () => {
    const items = Array.from({ length: 1000 }, (_, i) => ({
      id: i,
      title: `Post ${i}`
    }));
    
    const wrapper = mount(PostList, {
      props: { items }
    });
    
    const startTime = performance.now();
    await wrapper.vm.$nextTick();
    const endTime = performance.now();
    
    // 验证渲染时间在合理范围内(例如 100ms)
    expect(endTime - startTime).toBeLessThan(100);
  });
});

3.5.2 内存泄漏测试

// 必须修复:内存泄漏
// 不推荐 - 未清理的事件监听器
export default {
  mounted() {
    window.addEventListener('resize', this.handleResize);
  }
}

// 推荐 - 在组件销毁时清理事件监听器
export default {
  mounted() {
    window.addEventListener('resize', this.handleResize);
  },
  beforeUnmount() {
    window.removeEventListener('resize', this.handleResize);
  }
}

// 推荐 - 使用 onUnmounted 清理
import { onMounted, onUnmounted } from 'vue';

export default {
  setup() {
    const handleResize = () => {
      // 处理窗口大小变化
    };
    
    onMounted(() => {
      window.addEventListener('resize', handleResize);
    });
    
    onUnmounted(() => {
      window.removeEventListener('resize', handleResize);
    });
  }
}
// 内存泄漏测试用例示例
import { describe, it, expect, vi } from 'vitest';
import { mount } from '@vue/test-utils';
import MyComponent from './MyComponent.vue';

describe('Memory Leak Prevention', () => {
  it('should remove event listeners on unmount', () => {
    const addEventListenerSpy = vi.spyOn(window, 'addEventListener');
    const removeEventListenerSpy = vi.spyOn(window, 'removeEventListener');
    
    const wrapper = mount(MyComponent);
    
    // 验证事件监听器已添加
    expect(addEventListenerSpy).toHaveBeenCalled();
    
    wrapper.unmount();
    
    // 验证事件监听器已移除
    expect(removeEventListenerSpy).toHaveBeenCalled();
    expect(removeEventListenerSpy.mock.calls.length).toBe(addEventListenerSpy.mock.calls.length);
  });
});

4. 测试最佳实践

4.1 测试设计原则

  • AAA 原则:Arrange(准备)、Act(执行)、Assert(断言)
  • 单一职责原则:每个测试只测试一个功能点
  • DRY 原则:Don't Repeat Yourself,避免重复的测试代码
  • FIRST 原则
    • Fast:测试执行要快
    • Independent:测试之间要独立
    • Repeatable:测试结果要可重复
    • Self-validating:测试要自动验证结果
    • Timely:测试要及时编写

4.2 测试覆盖率

  • 目标覆盖率:核心功能的测试覆盖率应达到 80% 以上
  • 覆盖率指标
    • 行覆盖率:测试执行的代码行数占总代码行数的比例
    • 分支覆盖率:测试执行的分支占总分支数的比例
    • 函数覆盖率:测试执行的函数占总函数数的比例
    • 语句覆盖率:测试执行的语句占总语句数的比例

4.3 测试执行

  • 本地执行:开发人员在本地开发过程中执行测试
  • CI 执行:在持续集成过程中自动执行测试
  • 定时执行:定期执行完整的测试套件
  • 按需执行:根据需要执行特定的测试用例

4.4 测试报告

  • 生成测试报告:使用工具生成可视化的测试报告
  • 分析测试结果:定期分析测试结果,找出问题和改进点
  • 分享测试报告:将测试报告分享给团队成员
  • 持续改进:根据测试报告持续改进测试策略和代码质量

5. 持续集成

Tagtag Starter 项目使用 GitHub Actions 进行持续集成,自动执行测试。

5.1 CI 流程

  1. 代码提交:开发者将代码提交到 GitHub
  2. 触发 CI:GitHub Actions 自动触发 CI 流程
  3. 安装依赖:安装项目依赖
  4. 运行测试:执行单元测试、集成测试和 API 测试
  5. 生成报告:生成测试覆盖率报告
  6. 部署应用:如果测试通过,部署应用到测试环境

5.2 CI 配置示例

name: CI

on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main, develop ]

jobs:
  test:
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v3
    
    - name: Set up JDK 17
      uses: actions/setup-java@v3
      with:
        java-version: '17'
        distribution: 'temurin'
        cache: maven
    
    - name: Build with Maven
      run: mvn -B package --file pom.xml
    
    - name: Run tests
      run: mvn test
    
    - name: Upload test results
      uses: actions/upload-artifact@v3
      with:
        name: test-results
        path: tagtag-*/target/surefire-reports/

6. 总结

测试是保证软件质量的重要手段,Tagtag Starter 项目采用多层次的测试策略,包括单元测试、集成测试和 API 测试。通过遵循测试最佳实践,使用合适的测试工具,我们可以提高代码质量,减少回归问题,支持持续集成,确保项目的长期稳定发展。

希望本文档能够帮助开发者了解 Tagtag Starter 项目的测试方法和最佳实践,编写高质量的测试用例,共同维护一个高质量的代码库。