# Template Coverage URL: /docs/advanced/coverage/ Section: advanced Tags: advanced, coverage, testing -------------------------------------------------------------------------------- Template Coverage Kida can track which template lines execute during rendering and export coverage data in standard formats (LCOV, Cobertura XML). Coverage collection is opt-in — it has zero overhead when disabled because the compiler-emitted _rc.line = N markers only record hits when a CoverageCollector is active. from kida.coverage import CoverageCollector Quick Start from kida import Environment, FileSystemLoader from kida.coverage import CoverageCollector env = Environment(loader=FileSystemLoader("templates/")) cov = CoverageCollector() with cov: template = env.get_template("page.html") template.render(title="Hello") print(cov.summary()) cov.write_lcov("coverage.lcov") Output: Template Lines Hit Cover ----------------------------------------------------------------------- page.html 8 8 100.0% ----------------------------------------------------------------------- TOTAL 8 8 100.0% CoverageCollector API CoverageCollector is the primary interface. It dynamically patches RenderContext.__setattr__ while active, so there is zero overhead when no collector is running. Context Manager The recommended usage is as a context manager: with CoverageCollector() as cov: template.render(**context) # cov now contains all line hits from the render Manual Start / Stop For longer-lived collection (e.g., across an entire test suite): cov = CoverageCollector() cov.start() # ... render many templates ... cov.stop() print(cov.summary()) Calling start() on an already-started collector is a no-op. Calling stop() resets the ContextVar token and decrements the internal reference count. Raw Data Access the raw coverage data directly: cov.data # {"page.html": {1, 3, 5, 7, 8}, "base.html": {2, 4, 6}} The data property returns a dict[str, set[int]] mapping template names to sets of executed line numbers. Clearing Data Reset collected data without creating a new collector: cov.clear() Getting Results get_results() returns a list of CoverageResult objects, one per template, sorted by name: results = cov.get_results() for r in results: print(f"{r.template_name}: {r.hit_count}/{r.total_count} ({r.percentage:.1f}%)") Pass an optional source_map to provide full line enumeration for accurate miss counting: source_map = { "page.html": frozenset({1, 2, 3, 4, 5, 6, 7, 8, 9, 10}), } results = cov.get_results(source_map=source_map) for r in results: print(f"{r.template_name}: {r.missed_count} lines missed") Without a source_map, total_lines equals executed_lines (100% coverage for all touched templates). This is useful for hit tracking even without full line enumeration. CoverageResult Property Type Description template_name str Template identifier executed_lines frozenset[int] Line numbers that executed total_lines frozenset[int] All trackable line numbers hit_count int Number of executed lines (property) total_count int Total trackable lines (property) missed_count int Lines not executed (property) percentage float Coverage percentage (property) Text Summary summary() returns a formatted text table of coverage across all templates: print(cov.summary(source_map=source_map)) If no templates were rendered, summary() returns "No templates rendered.". LCOV Export Write coverage data in LCOV tracefile format, compatible with lcov, genhtml, and most CI coverage tools: cov.write_lcov("coverage.lcov") Or get the LCOV content as a string: lcov_string = cov.format_lcov() The output follows the standard LCOV format: SF:page.html DA:1,1 DA:3,1 DA:5,1 LH:3 LF:3 end_of_record Each DA: line records a line number and its hit count (always 1 for any executed line). LH is the number of lines hit, LF is the total number of lines in the record. Cobertura Export Write coverage data in Cobertura XML format, compatible with Jenkins, GitLab CI, and other CI systems: cov.write_cobertura("coverage.xml") Or get the XML content as a string: xml_string = cov.format_cobertura() The output follows the Cobertura schema. Templates are grouped under a single "templates" package, with each template represented as a class. The root element includes a line-rate attribute with the aggregate coverage ratio. Thread Safety CoverageCollector uses ContextVar for data isolation and a threading.Lock for the global reference count. This means: Concurrent renders — each collector tracks only its own renders; parallel test runners or async handlers do not interfere with each other. Nested collectors — multiple CoverageCollector instances can be active simultaneously. The RenderContext.__setattr__ patch stays active as long as at least one collector is running (reference-counted via _active_count). Cleanup — when the last active collector stops, the __setattr__ patch is removed entirely, restoring zero-overhead rendering. CI Integration Example pytest + LCOV import pytest from kida.coverage import CoverageCollector @pytest.fixture(scope="session") def template_coverage(): cov = CoverageCollector() cov.start() yield cov cov.stop() cov.write_lcov("template-coverage.lcov") cov.write_cobertura("template-coverage.xml") def test_homepage(template_coverage, env): template = env.get_template("home.html") result = template.render(title="Home") assert "Home" in result def test_about(template_coverage, env): template = env.get_template("about.html") result = template.render(title="About") assert "About" in result GitHub Actions - name: Run tests with template coverage run: pytest --tb=short - name: Upload template coverage uses: codecov/codecov-action@v4 with: files: template-coverage.lcov flags: templates GitLab CI test: script: - pytest --tb=short artifacts: reports: coverage_report: coverage_format: cobertura path: template-coverage.xml See Also Profiling — Render-time instrumentation Static Analysis — Dependency and purity analysis -------------------------------------------------------------------------------- Metadata: - Word Count: 713 - Reading Time: 4 minutes