从Linter走到自动化测试:代码质量提升笔记
这次做代码质量提升,从 Linter 到自动化测试,。
Bug 太多,维护困难。
Linter 静态检查
ESLint
// .eslintrc.js
module.exports = {
env: {
browser: true,
es2021: true,
node: true,
},
extends: [
'eslint:recommended',
'plugin:react/recommended',
'plugin:@typescript-eslint/recommended',
'prettier',
],
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaFeatures: {
jsx: true,
},
ecmaVersion: 'latest',
sourceType: 'module',
},
plugins: ['react', '@typescript-eslint'],
rules: {
'no-console': 'warn',
'no-unused-vars': 'error',
'react/prop-types': 'off',
'@typescript-eslint/no-explicit-any': 'warn',
},
};
Prettier
// .prettierrc.js
module.exports = {
semi: true,
trailingComma: 'es5',
singleQuote: true,
printWidth: 100,
tabWidth: 2,
useTabs: false,
};
Go Linter
// golangci-lint 配置
// .golangci.yml
linters:
disable-all: true
enable:
- errcheck
- gosimple
- govet
- ineffassign
- staticcheck
- typecheck
- unused
- gofmt
- goimports
- misspell
linters-settings:
govet:
enable-all: true
自动化测试
单元测试
// Jest 单元测试
describe('Calculator', () => {
let calculator;
beforeEach(() => {
calculator = new Calculator();
});
test('add should return sum of two numbers', () => {
expect(calculator.add(1, 2)).toBe(3);
});
test('subtract should return difference of two numbers', () => {
expect(calculator.subtract(5, 3)).toBe(2);
});
test('multiply should return product of two numbers', () => {
expect(calculator.multiply(2, 3)).toBe(6);
});
});
// 使用 Mock
describe('UserService', () => {
test('getUser should return user', async () => {
const mockDb = {
query: jest.fn().mockResolvedValue([{ id: 1, name: 'Alice' }]),
};
const userService = new UserService(mockDb);
const user = await userService.getUser(1);
expect(user).toEqual({ id: 1, name: 'Alice' });
expect(mockDb.query).toHaveBeenCalledWith(
'SELECT * FROM users WHERE id = ?',
[1]
);
});
});
集成测试
// Supertest 集成测试
const request = require('supertest');
const app = require('./app');
describe('User API', () => {
test('GET /api/users should return users', async () => {
const response = await request(app)
.get('/api/users')
.expect('Content-Type', /json/)
.expect(200);
expect(Array.isArray(response.body)).toBe(true);
});
test('POST /api/users should create user', async () => {
const newUser = { name: 'Alice', email: '[email protected]' };
const response = await request(app)
.post('/api/users')
.send(newUser)
.expect('Content-Type', /json/)
.expect(201);
expect(response.body).toHaveProperty('id');
expect(response.body.name).toBe(newUser.name);
});
});
E2E 测试
// Cypress E2E 测试
describe('User Login', () => {
beforeEach(() => {
cy.visit('/login');
});
it('should login with valid credentials', () => {
cy.get('[data-testid="username"]').type('testuser');
cy.get('[data-testid="password"]').type('password123');
cy.get('[data-testid="submit"]').click();
cy.url().should('include', '/dashboard');
cy.get('[data-testid="welcome"]').should('contain', 'Welcome');
});
it('should show error with invalid credentials', () => {
cy.get('[data-testid="username"]').type('testuser');
cy.get('[data-testid="password"]').type('wrongpassword');
cy.get('[data-testid="submit"]').click();
cy.get('[data-testid="error"]').should('contain', 'Invalid credentials');
});
});
代码覆盖率
配置覆盖率
// Jest 配置
module.exports = {
collectCoverage: true,
collectCoverageFrom: [
'src/**/*.js',
'!src/**/*.test.js',
'!src/**/*.spec.js',
],
coverageThreshold: {
global: {
branches: 80,
functions: 80,
lines: 80,
statements: 80,
},
},
coverageReporters: ['text', 'lcov', 'html'],
};
生成覆盖率报告
# 运行测试并生成覆盖率报告
npm test -- --coverage
# 查看覆盖率报告
open coverage/lcov-report/index.html
CI/CD 集成
GitHub Actions
# .github/workflows/test.yml
name: Test
on:
pull_request:
branches: [ main ]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup Node.js
uses: actions/setup-node@v2
with:
node-version: '16'
- name: Install dependencies
run: npm ci
- name: Run Linter
run: npm run lint
- name: Run Tests
run: npm test
- name: Upload coverage
uses: codecov/codecov-action@v2
with:
files: ./coverage/lcov.info
GitLab CI/CD
# .gitlab-ci.yml
stages:
- test
- quality
lint:
stage: test
script:
- npm run lint
test:
stage: test
script:
- npm test -- --coverage
coverage: '/All files[^|]*\|[^|]*\s+([\d\.]+)/'
artifacts:
reports:
coverage_report:
coverage_format: cobertura
path: coverage/cobertura-coverage.xml
code_quality:
stage: quality
script:
- npm run code-quality
代码审查
Pull Request 模板
# Pull Request 模板
## 变更描述
<!-- 简要描述这个 PR 做了什么 -->
## 变更类型
<!-- 选择一个 -->
- [ ] Bug 修复
- [ ] 新功能
- [ ] 性能优化
- [ ] 重构
- [ ] 文档更新
## 测试
<!-- 描述如何测试这个 PR -->
## 检查清单
- [ ] 代码遵循项目规范
- [ ] 添加了必要的测试
- [ ] 更新了相关文档
- [ ] 没有引入新的警告
自动化检查
// 使用 Husky 和 lint-staged
{
"husky": {
"hooks": {
"pre-commit": "lint-staged",
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
}
},
"lint-staged": {
"*.{js,jsx,ts,tsx}": [
"eslint --fix",
"prettier --write",
"jest --bail --findRelatedTests"
]
}
}
踩过的坑
坑一:测试不够
覆盖率看似很高,但测试质量不够。
解决:关注测试质量,不只是覆盖率。
// 好的测试:测试边界条件
test('should handle empty array', () => {
expect(calculateAverage([])).toBe(0);
});
test('should handle negative numbers', () => {
expect(calculateAverage([-1, -2, -3])).toBe(-2);
});
test('should handle decimal numbers', () => {
expect(calculateAverage([1.5, 2.5, 3.5])).toBe(2.5);
});
坑二:测试太慢
测试运行太慢,影响开发效率。
解决:使用 Mock,并行运行测试。
// 使用 Mock 加速测试
jest.mock('./api', () => ({
fetchUser: jest.fn().mockResolvedValue({ id: 1, name: 'Alice' }),
}));
// 并行运行测试
jest --maxWorkers=4
坑三:Linter 太严格
Linter 规则太严格,影响开发效率。
解决:平衡严格性和实用性。
// .eslintrc.js
module.exports = {
rules: {
// 实用规则
'no-console': 'warn',
'no-unused-vars': 'error',
// 灵活规则
'@typescript-eslint/no-explicit-any': 'warn',
'react/prop-types': 'off',
},
};
写在最后
代码质量这东西,不是技术问题,是文化和习惯问题。
解决了:
- Bug 减少
- 维护成本降低
- 代码可读性提高
带来了:
- 开发时间增加
- 学习成本
- 规则维护
提升代码质量之前先评估:
- 项目规模
- 团队能力
- 时间预算
- 质量要求
不是所有项目都需要严格的代码质量控制,但基础规范不能少。
这次代码质量提升花了一个月,从 Linter 到自动化测试,再到 CI/CD 集成。提升完成后,Bug 数量减少了 70%,代码审查时间减少了 50%。
版权声明: 本文首发于 指尖魔法屋-从Linter走到自动化测试:代码质量提升笔记(https://blog.thinkmoon.cn/post/79-code-quality-linter-automated-testing-practice/) 转载或引用必须申明原指尖魔法屋来源及源地址!
评论
使用 GitHub 账号登录后即可留言,支持 Markdown。