Есть ли способ получить покрытие кода nodejs, когда тесты написаны на python (pytest)? - PullRequest
0 голосов
/ 01 сентября 2018

Я написал тесты Mocha в моем предыдущем проекте. Хорошая вещь об этом - инструмент покрытия кода Стамбула. Это очень полезно и круто. Сейчас я использую pytest для моего текущего проекта. Некоторые сервисы являются приложениями nodejs. Теперь мой вопрос: есть ли способ получить покрытие кода для приложения nodejs, когда я использую pytest?

1 Ответ

0 голосов
/ 09 сентября 2018

Объединение отчетов о покрытии инструментов istanbul и coverage имеет две основные проблемы:

  1. Естественно, coverage ничего не знает о файлах javascript, поэтому мы должны как-то зарегистрировать их в прогоне покрытия - это будет сделано путем реализации пользовательского плагина.
  2. Мы должны преобразовать istanbul отчет о покрытии в формат coverage понимает.

Настройка

Поскольку фрагмент кода был бы слишком большим, чтобы его можно было вставить непосредственно в ответ, я подготовил git-репозиторий , с которым вы можете воспроизвести тестовый прогон (конечно, вы можете повторно использовать код по своему усмотрению) :

$ git clone https://github.com/hoefling/stackoverflow-52124836
$ cd stackoverflow-52124836/
$ yarn install

Сначала создайте отчет о покрытии в Стамбуле:

$ yarn test
  yarn run v1.9.4
  warning package.json: No license field
  $ istanbul cover _mocha js

  Array
    #length
      ✓ should be 0 when the array is empty
      ✓ should be 1 when the array has one element
      ✓ should be 2 when the array has two elements

  Array
    #indexOf()
      ✓ should return -1 when the value is not present


  4 passing (5ms)

=============================================================================
Writing coverage object [/private/tmp/stackoverflow-52124836/coverage/coverage.json]
Writing coverage reports at [/private/tmp/stackoverflow-52124836/coverage]
=============================================================================

=============================== Coverage summary ===============================
Statements   : 100% ( 14/14 )
Branches     : 100% ( 0/0 )
Functions    : 100% ( 8/8 )
Lines        : 100% ( 14/14 )
================================================================================

Теперь запускайте тесты на Python с pytest:

$ python -m pytest -sv --cov=py --cov=js --cov-report=term-missing
=================================== test session starts ===================================
platform darwin -- Python 3.6.4, pytest-3.7.3, py-1.5.4, pluggy-0.7.1 --
 /Users/hoefling/.virtualenvs/stackoverflow/bin/python
cachedir: .pytest_cache
rootdir: /private/tmp/stackoverflow-52124836, inifile:
plugins: cov-2.5.1
collected 1 item

py/test_spam.py::test_spam PASSED

---------- coverage: platform darwin, python 3.6.4-final-0 -----------
Name                      Stmts   Miss  Cover   Missing
-------------------------------------------------------
js/array.length.spec.js      14      0   100%
js/array.spec.js              8      0   100%
py/test_spam.py               2      0   100%
-------------------------------------------------------
TOTAL                        24      0   100%

================================ 1 passed in 0.56 seconds =================================

js / py dirs

Это только некоторые примеры тестовых файлов для воспроизведения. Чтобы упростить настройку, istanbul собирает покрытие по тестовому коду.

mycov

Содержит плагин для coverage. Для получения подробной информации о том, как писать плагины для coverage, обратитесь к Классы плагинов ; здесь я просто объясняю соответствующие пятна:

class IstanbulPlugin(coverage.plugin.CoveragePlugin, coverage.plugin.FileTracer):

    def file_reporter(self, filename):
        return FileReporter(filename)

    def file_tracer(self, filename):
        return None

    def find_executable_files(self, src_dir):
        yield from (str(p) for p in pathlib.Path(src_dir).rglob('*.js')
                    if not any(d in p.parts for d in ('node_modules', 'coverage',)))

Класс плагина ничего не делает, кроме поиска файлов javascript и регистрации их как выполненных в coverage run (метод find_executable_files). Он не записывает покрытие кода вообще! Он также регистрирует простой файл-репортер для файлов JavaScript:

class FileReporter(coverage.plugin.FileReporter):

    def source(self):
        with open(self.filename) as fp:
            js = fp.read()
        return js

    def lines(self):
        return {i + 1 for i, line in enumerate(self.source().split(os.linesep)) if line.strip()}

Репортер возвращает исходный код файлов javascript как есть, исполняемые строки - это все строки кода, которые не являются пустыми.


Примечание 1:

Этого не достаточно! Например, строки комментария к строке и блоку будут считаться исполняемыми. Вам нужно будет адаптировать метод lines; Лучше всего использовать синтаксический анализатор кода JavaScript, который извлекает информацию об исполняемых строках.

Примечание 2:

И istanbul, и coverage считают строки от 1, а не от 0, таким образом, смещение номеров строк.


Теперь вам нужно зарегистрировать плагин через coverage_init:

# mycov/__init__.py

def coverage_init(reg, options):
    reg.add_file_tracer(IstanbulPlugin())

и добавьте пользовательский плагин в .coveragerc:

[run]
plugins = mycov

Добавить istanbul отчет к coverage

Теперь, когда coverage знает, что файлы javascript необходимо учитывать, мы объединяем покрытие js с покрытием python. За это отвечает прибор append_istanbul_coverage в conftest.py.

@pytest.fixture(autouse=True)
def append_istanbul_coverage(cov):
    yield

    with open('coverage/coverage.json') as fp:
        data = json.load(fp)
    converted = {'lines': {item['path']: line_numbers(item) for item in data.values()}}
    text = "!coverage.py: This is a private format, don't read it directly!" + json.dumps(converted)
    istanbul_cov = coverage.data.CoverageData()

    with io.StringIO(text) as fp:
        istanbul_cov.read_fileobj(fp)
    cov.data.update(istanbul_cov)

Прибор будет автоматически выполняться один раз за тестовый прогон, код после yield будет запускаться, когда все тесты будут выполнены. Приспособление cov предоставляется pytest-cov; мы используем его для доступа к текущему объекту данных coverage. Сначала мы читаем покрытие istanbul; затем преобразуйте его в строку, которая может быть понята как coverage - это просто json с добавленным специальным сообщением. После этого остается только обновить текущие coverage данные, мы готовы!

...