CtrlK
BlogDocsLog inGet started
Tessl Logo

test-runner

Use this skill when the user wants to run tests, check test coverage, set up a test suite, parse test results, or integrate testing into CI/CD.

69

Quality

84%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide

SecuritybySnyk

Passed

No findings from the security scan

SKILL.md
Quality
Evals
Security

Test Runner

Overview

Runs pytest test suites, measures coverage, parallelizes test execution, and generates summary reports. Parses JUnit XML for CI integration and produces human-readable markdown summaries.

Run Tests

# Basic run
pytest tests/ -v

# With coverage
pytest tests/ --cov=src --cov-report=term-missing --cov-report=html

# Parallel (install pytest-xdist)
pytest tests/ -n auto          # use all CPU cores
pytest tests/ -n 4             # 4 parallel workers

# Stop on first failure
pytest tests/ -x

# Run only failed tests from last run
pytest tests/ --lf

# Run tests matching a pattern
pytest tests/ -k "auth or login"

# Specific test file/function
pytest tests/test_auth.py::test_login_success

Programmatic Test Execution

import subprocess, json

def run_tests(test_path: str = "tests/", extra_args: list[str] | None = None) -> dict:
    cmd = [
        "python", "-m", "pytest", test_path,
        "--tb=short", "-q",
        f"--junit-xml=test_results.xml",
        "--json-report", "--json-report-file=test_results.json"
    ]
    if extra_args:
        cmd.extend(extra_args)
    result = subprocess.run(cmd, capture_output=True, text=True)
    return {
        "exit_code": result.returncode,
        "passed": result.returncode == 0,
        "stdout": result.stdout,
        "stderr": result.stderr,
    }

Parse JUnit XML

import xml.etree.ElementTree as ET
from pathlib import Path

def parse_junit_xml(xml_path: str = "test_results.xml") -> dict:
    tree = ET.parse(xml_path)
    root = tree.getroot()
    suite = root if root.tag == "testsuite" else root.find("testsuite")
    return {
        "tests":    int(suite.get("tests", 0)),
        "failures": int(suite.get("failures", 0)),
        "errors":   int(suite.get("errors", 0)),
        "skipped":  int(suite.get("skipped", 0)),
        "time_s":   float(suite.get("time", 0)),
        "passed":   int(suite.get("tests", 0))
                    - int(suite.get("failures", 0))
                    - int(suite.get("errors", 0))
                    - int(suite.get("skipped", 0)),
        "failed_tests": [
            {"name": tc.get("name"), "classname": tc.get("classname"),
             "message": tc.find("failure").get("message", "") if tc.find("failure") is not None else ""}
            for tc in suite.findall(".//testcase")
            if tc.find("failure") is not None or tc.find("error") is not None
        ],
    }

Coverage Report

import subprocess, re

def get_coverage_summary(source: str = "src/") -> dict:
    result = subprocess.run(
        ["python", "-m", "coverage", "report", "--include=src/**"],
        capture_output=True, text=True
    )
    # Parse total line
    match = re.search(r"TOTAL\s+\d+\s+\d+\s+(\d+)%", result.stdout)
    total_pct = int(match.group(1)) if match else 0
    # Find files below threshold
    low_coverage = []
    for line in result.stdout.splitlines():
        m = re.match(r"(\S+)\s+\d+\s+\d+\s+(\d+)%", line)
        if m and int(m.group(2)) < 80:
            low_coverage.append({"file": m.group(1), "coverage": int(m.group(2))})
    return {"total_pct": total_pct, "low_coverage": low_coverage}

Markdown Summary Report

def format_test_summary(results: dict, coverage: dict | None = None) -> str:
    status = "PASSED" if results["passed"] == results["tests"] else "FAILED"
    lines = [
        f"## Test Results — {status}",
        "",
        f"| Metric | Value |",
        f"|--------|-------|",
        f"| Total  | {results['tests']} |",
        f"| Passed | {results['passed']} |",
        f"| Failed | {results['failures'] + results['errors']} |",
        f"| Skipped | {results['skipped']} |",
        f"| Duration | {results['time_s']:.2f}s |",
    ]
    if coverage:
        lines.append(f"| Coverage | {coverage['total_pct']}% |")

    if results.get("failed_tests"):
        lines += ["", "### Failed Tests", ""]
        for t in results["failed_tests"]:
            lines.append(f"- **{t['classname']}::{t['name']}**")
            if t["message"]:
                lines.append(f"  `{t['message'][:100]}`")
    return "\n".join(lines)

pytest.ini / pyproject.toml Config

# pyproject.toml
[tool.pytest.ini_options]
testpaths = ["tests"]
asyncio_mode = "auto"
addopts = [
    "--tb=short",
    "--strict-markers",
    "-q",
]

[tool.coverage.run]
source = ["src"]
omit = ["*/tests/*", "*/__pycache__/*"]

[tool.coverage.report]
fail_under = 80
show_missing = true

Quick Reference

TaskCommandNotes
Run all testspytest tests/ -vVerbose output
With coveragepytest --cov=srcAdd --cov-report=html
Parallelpytest -n autoRequires pytest-xdist
Only failurespytest --lfLast-failed
JUnit XMLpytest --junit-xml=out.xmlCI integration
Coverage %coverage reportAfter coverage run
Fail below %coverage report --fail-under=80CI gate
Repository
ProwlrBot/prowlr-marketplace
Last updated
First committed

Is this your skill?

If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.