8

I am writing integration tests for a project in which I am making HTTP calls and testing whether they were successful or not.

Since I am not importing any module and not calling functions directly coverage.py report for this is 0%.

I want to know how can I generate coverage report for such integration HTTP request tests?

2 Answers 2

11

The recipe is pretty much this:

  1. Ensure the backend starts in code coverage mode
  2. Run the tests
  3. Ensure the backend coverage is written to file
  4. Read the coverage from file and append it to test run coverage

Example:

backend

Imagine you have a dummy backend server that responds with a "Hello World" page on GET requests:

# backend.py
from http.server import BaseHTTPRequestHandler, HTTPServer


class DummyHandler(BaseHTTPRequestHandler):

    def do_GET(self):
        self.send_response(200)
        self.send_header('Content-Type', 'text/html')
        self.end_headers()
        self.wfile.write('<html><body><h1>Hello World</h1></body></html>'.encode())


if __name__ == '__main__':
    HTTPServer(('127.0.0.1', 8000), DummyHandler).serve_forever()

test

A simple test that makes an HTTP request and verifies the response contains "Hello World":

# tests/test_server.py
import requests


def test_GET():
    resp = requests.get('http://127.0.0.1:8000')
    resp.raise_for_status()
    assert 'Hello World' in resp.text

Recipe

# tests/conftest.py
import os
import signal
import subprocess
import time
import coverage.data
import pytest



@pytest.fixture(autouse=True)
def run_backend(cov):
    # 1.
    env = os.environ.copy()
    env['COVERAGE_FILE'] = '.coverage.backend'
    serverproc = subprocess.Popen(['coverage', 'run', 'backend.py'], env=env,
                                  stdout=subprocess.PIPE,
                                  stderr=subprocess.PIPE,
                                  preexec_fn=os.setsid)
    time.sleep(3)
    yield  # 2.
    # 3.
    serverproc.send_signal(signal.SIGINT)
    time.sleep(1)
    # 4.
    backendcov = coverage.data.CoverageData()
    with open('.coverage.backend') as fp:
        backendcov.read_fileobj(fp)
    cov.data.update(backendcov)

cov is the fixture provided by pytest-cov (docs).

Running the test adds the coverage of backend.py to the overall coverage, although only tests selected:

$ pytest --cov=tests --cov-report term -vs
=============================== test session starts ===============================
platform linux -- Python 3.6.5, pytest-3.4.1, py-1.5.3, pluggy-0.6.0 -- 
/data/gentoo64/usr/bin/python3.6
cachedir: .pytest_cache
rootdir: /data/gentoo64/home/u0_a82/projects/stackoverflow/so-50689940, inifile:
plugins: mock-1.6.3, cov-2.5.1
collected 1 item

tests/test_server.py::test_GET PASSED

----------- coverage: platform linux, python 3.6.5-final-0 -----------
Name                   Stmts   Miss  Cover
------------------------------------------
backend.py                12      0   100%
tests/conftest.py         18      0   100%
tests/test_server.py       5      0   100%
------------------------------------------
TOTAL                     35      0   100%


============================ 1 passed in 5.09 seconds =============================
Sign up to request clarification or add additional context in comments.

3 Comments

I am getting an error with above coverage overwriting method. The .coverage is being overwritten when pytest is run. I am getting this error coverage.misc.CoverageException: Doesn't seem to be a coverage.py data file
This is probably due to conflicting versions of coverage package in pytest and on backend side, related issue.
With recent changes in I think coverage 5.4 I think the last four lines should instead read backendcov = coverage.data.CoverageData(basename='.coverage.backend') backendcov.read() cov.get_data().update(backendcov)
2

With Coverage 5.1, based on the "Measuring sub-processes" section of the coverage.py docs, you can set the COVERAGE_PROCESS_START env-var, call the coverage.process_startup() somewhere in your code. If you set parallel=True in your .coveragerc

  1. Somewhere in your process, call this code:

    import coverage
    coverage.process_startup()
    

    This can be done in sitecustomize.py globally, but in my case it was easy to add this to my application's __init__.py, where I added:

    import os
    if 'COVERAGE_PROCESS_START' in os.environ:
        import coverage
        coverage.process_startup()
    

    Just to be safe, I added an additional check to this if statement (checking if MYAPP_COVERAGE_SUBPROCESS is also set)

  2. In your test case, set the COVERAGE_PROCESS_START to the path to your .coveragerc file (or an empty string if don't need this config), for example:

    import os
    import subprocess
    
    env = os.environ.copy()
    env['COVERAGE_PROCESS_START'] = '.coveragerc'
    
    cmd = [sys.executable, 'run_my_app.py']
    
    p  = subprocess.Popen(cmd, env=env)
    p.communicate()
    assert p.returncode == 0 # ..etc
    
  3. Finally, you create .coveragerc containing:

    [run]
    parallel = True
    source = myapp # Which module to collect coverage for
    

    This ensures the .coverage files created by each process go to a unique file, which pytest-cov appears to merge automatically (or can be done manually with coverage combine). It also describes which modules to collect data for (the --cov=myapp arg doesn't get passed to child processes)

  4. To run your tests, just invoke pytest --cov=

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.