如何编写统计代码覆盖率脚本

wen 实用脚本 28

本文目录导读:

如何编写统计代码覆盖率脚本

  1. Python 覆盖率统计
  2. JavaScript/TypeScript 覆盖率
  3. Java 覆盖率统计
  4. 通用覆盖率统计脚本
  5. 配置示例 (.coveragerc)
  6. CI/CD 集成脚本

我来介绍几种常见的代码覆盖率统计脚本编写方法,根据不同语言和工具进行分类:

Python 覆盖率统计

使用 pytest-cov

#!/bin/bash
# coverage_python.sh
# 安装依赖
pip install pytest pytest-cov coverage
# 运行测试并生成覆盖率报告
pytest --cov=./src \
       --cov-report=term-missing \
       --cov-report=html:coverage_html \
       --cov-report=xml:coverage.xml \
       tests/

更完整的 Python 脚本

#!/usr/bin/env python3
# coverage_stats.py
import subprocess
import sys
import os
def run_coverage():
    """运行覆盖率统计"""
    # 配置参数
    source_dir = 'src'  # 源代码目录
    test_dir = 'tests'  # 测试目录
    output_dir = 'coverage_reports'
    # 创建输出目录
    os.makedirs(output_dir, exist_ok=True)
    # 运行覆盖率检查
    commands = [
        f'coverage run --source={source_dir} -m pytest {test_dir}',
        f'coverage report -m',
        f'coverage html -d {output_dir}/html',
        f'coverage xml -o {output_dir}/coverage.xml'
    ]
    for cmd in commands:
        print(f"执行: {cmd}")
        result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
        if result.returncode != 0:
            print(f"错误: {result.stderr}")
            sys.exit(1)
        print(result.stdout)
    # 解析覆盖率数据
    print(f"\n覆盖率报告已生成到: {output_dir}/html/")
if __name__ == '__main__':
    run_coverage()

JavaScript/TypeScript 覆盖率

使用 Istanbul/nyc

// package.json 配置
{
  "scripts": {
    "coverage": "nyc --reporter=html --reporter=text --reporter=lcov npm test",
    "coverage:check": "nyc --check-coverage --lines=80 --functions=80 --branches=80 npm test"
  }
}

Shell 脚本版本

#!/bin/bash
# coverage_js.sh
# 配置
COVERAGE_THRESHOLD=80
PROJECT_DIR=$(pwd)
echo "=== JavaScript 覆盖率统计 ==="
# 安装依赖
npm install --save-dev nyc mocha
# 运行测试并生成覆盖率
npx nyc \
    --reporter=html \
    --reporter=text \
    --reporter=lcov \
    --report-dir=./coverage \
    npm test
# 检查覆盖率是否达标
if [ $? -eq 0 ]; then
    echo "覆盖率报告生成成功"
    # 解析覆盖率数据
    COVERAGE=$(npx nyc report --reporter=text-summary | grep Lines | awk '{print $3}' | sed 's/%//')
    echo "当前行覆盖率: ${COVERAGE}%"
    if (( $(echo "$COVERAGE < $COVERAGE_THRESHOLD" | bc -l) )); then
        echo "警告: 覆盖率低于 ${COVERAGE_THRESHOLD}%"
        exit 1
    fi
else
    echo "覆盖率测试失败"
    exit 1
fi

Java 覆盖率统计

使用 JaCoCo

<!-- pom.xml 配置 -->
<plugin>
    <groupId>org.jacoco</groupId>
    <artifactId>jacoco-maven-plugin</artifactId>
    <version>0.8.11</version>
    <executions>
        <execution>
            <goals>
                <goal>prepare-agent</goal>
            </goals>
        </execution>
        <execution>
            <id>report</id>
            <phase>test</phase>
            <goals>
                <goal>report</goal>
            </goals>
        </execution>
        <execution>
            <id>check</id>
            <phase>verify</phase>
            <goals>
                <goal>check</goal>
            </goals>
            <configuration>
                <rules>
                    <rule>
                        <element>BUNDLE</element>
                        <limits>
                            <limit>
                                <counter>LINE</counter>
                                <value>COVEREDRATIO</value>
                                <minimum>0.80</minimum>
                            </limit>
                        </limits>
                    </rule>
                </rules>
            </configuration>
        </execution>
    </executions>
</plugin>

Shell 脚本版本

#!/bin/bash
# coverage_java.sh
# 配置
MIN_COVERAGE=80
JACOCO_VERSION=0.8.11
echo "=== Java 覆盖率统计 ==="
# 检查工具
command -v mvn >/dev/null 2>&1 || { echo "需要 Maven"; exit 1; }
# 运行测试并生成覆盖率
mvn clean test jacoco:report
# 检查覆盖率
if [ -f "target/site/jacoco/jacoco.csv" ]; then
    # 解析 CSV 文件获取覆盖率
    LINE_COVERAGE=$(awk -F',' 'NR>1 {sum+=$8; count++} END {printf "%.2f", sum/count}' target/site/jacoco/jacoco.csv)
    echo "代码行覆盖率: ${LINE_COVERAGE}%"
    if (( $(echo "$LINE_COVERAGE < $MIN_COVERAGE" | bc -l) )); then
        echo "覆盖率低于 ${MIN_COVERAGE}%"
        exit 1
    fi
fi

通用覆盖率统计脚本

#!/usr/bin/env python3
# universal_coverage.py
import os
import sys
import json
import subprocess
from pathlib import Path
class CoverageCollector:
    """通用的覆盖率收集器"""
    def __init__(self, config_file='.coveragerc'):
        self.config = self._load_config(config_file)
        self.results = {}
    def _load_config(self, config_file):
        """加载配置文件"""
        default_config = {
            'python': {
                'enabled': True,
                'source': 'src',
                'test_dir': 'tests',
                'threshold': 80
            },
            'javascript': {
                'enabled': True,
                'test_dir': 'test',
                'threshold': 80
            },
            'java': {
                'enabled': True,
                'threshold': 80
            }
        }
        if os.path.exists(config_file):
            with open(config_file, 'r') as f:
                return json.load(f)
        return default_config
    def collect_python_coverage(self):
        """收集 Python 覆盖率"""
        print("\n=== Python 覆盖率 ===")
        try:
            cmd = [
                'coverage', 'run',
                f'--source={self.config["python"]["source"]}',
                '-m', 'pytest', self.config['python']['test_dir']
            ]
            subprocess.run(cmd, check=True)
            result = subprocess.run(
                ['coverage', 'report', '--format=json'],
                capture_output=True, text=True
            )
            if result.returncode == 0:
                data = json.loads(result.stdout)
                return {
                    'lines': data.get('totals', {}).get('percent_covered', 0),
                    'branches': data.get('totals', {}).get('percent_covered_branches', 0)
                }
        except Exception as e:
            print(f"Python 覆盖率失败: {e}")
        return None
    def collect_js_coverage(self):
        """收集 JavaScript 覆盖率"""
        print("\n=== JavaScript 覆盖率 ===")
        try:
            subprocess.run(
                ['npx', 'nyc', '--reporter=json', 'npm', 'test'],
                check=True
            )
            if os.path.exists('.nyc_output/coverage.json'):
                with open('.nyc_output/coverage.json', 'r') as f:
                    data = json.load(f)
                total_lines = total_covered = 0
                for file_data in data.values():
                    for line, count in file_data.get('s', {}).items():
                        total_lines += 1
                        if count > 0:
                            total_covered += 1
                return {
                    'lines': (total_covered / total_lines * 100) if total_lines > 0 else 0
                }
        except Exception as e:
            print(f"JavaScript 覆盖率失败: {e}")
        return None
    def generate_report(self):
        """生成覆盖率报告"""
        report = {
            'timestamp': None,
            'project': os.getcwd(),
            'results': {}
        }
        from datetime import datetime
        report['timestamp'] = datetime.now().isoformat()
        # 收集各语言覆盖率
        if self.config.get('python', {}).get('enabled', False):
            report['results']['python'] = self.collect_python_coverage()
        if self.config.get('javascript', {}).get('enabled', False):
            report['results']['javascript'] = self.collect_js_coverage()
        # 保存报告
        report_file = 'coverage_report.json'
        with open(report_file, 'w') as f:
            json.dump(report, f, indent=2)
        print(f"\n报告已保存到: {report_file}")
        # 检查覆盖率阈值
        self._check_thresholds(report)
        return report
    def _check_thresholds(self, report):
        """检查覆盖率是否达标"""
        all_passed = True
        for lang, data in report['results'].items():
            if data:
                threshold = self.config.get(lang, {}).get('threshold', 80)
                coverage = data.get('lines', 0)
                if coverage < threshold:
                    print(f"⚠ {lang}: {coverage:.1f}% (低于 {threshold}%)")
                    all_passed = False
                else:
                    print(f"✓ {lang}: {coverage:.1f}%")
        if not all_passed:
            print("\n❌ 部分语言未达到覆盖率要求")
            sys.exit(1)
        else:
            print("\n✅ 所有语言覆盖率达标")
if __name__ == '__main__':
    collector = CoverageCollector()
    collector.generate_report()

配置示例 (.coveragerc)

{
  "python": {
    "enabled": true,
    "source": "src",
    "test_dir": "tests",
    "threshold": 85,
    "exclude": ["tests/*", "migrations/*", "setup.py"]
  },
  "javascript": {
    "enabled": true,
    "test_dir": "test",
    "threshold": 80,
    "exclude": ["node_modules/*", "dist/*"]
  },
  "java": {
    "enabled": true,
    "threshold": 85,
    "exclude": ["test/*", "generated/*"]
  }
}

CI/CD 集成脚本

# .github/workflows/coverage.yml
name: Code Coverage
on: [push, pull_request]
jobs:
  coverage:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: Setup Python
        uses: actions/setup-python@v2
        with:
          python-version: '3.9'
      - name: Install dependencies
        run: |
          pip install pytest pytest-cov coverage
          npm install
      - name: Run coverage
        run: python coverage_universal.py
      - name: Upload coverage to Codecov
        uses: codecov/codecov-action@v2
        with:
          files: ./coverage.xml,./coverage/lcov.info
          flags: unittests
          name: codecov-umbrella

这些脚本可以根据具体项目需求进行调整和组合使用,选择哪种方案主要取决于:

  1. 项目使用的编程语言
  2. 现有的测试框架
  3. CI/CD 集成需求
  4. 是否需要多语言支持

建议从简单的单语言脚本开始,根据需要逐步扩展。

抱歉,评论功能暂时关闭!