从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/) 转载或引用必须申明原指尖魔法屋来源及源地址!