Вклад в кодовую базу#

Стандарты кода#

Написание хорошего кода — это не только о том, что вы пишете. Это также о how вы записываете. Во время Непрерывная интеграция при тестировании будет запущено несколько инструментов для проверки вашего кода на стилистические ошибки. Генерация любых предупреждений приведет к провалу теста. Таким образом, хороший стиль является обязательным требованием для отправки кода в pandas.

В pandas есть несколько инструментов, которые помогают участникам проверять свои изменения перед внесением в проект

  • ./ci/code_checks.sh: скрипт проверяет doctests, форматирование в docstrings, и импортированные модули. Можно запустить проверки независимо, используя параметры docstrings, code, и doctests (например, ./ci/code_checks.sh doctests);

  • pre-commit, что мы подробно рассмотрим в следующем разделе.

Кроме того, поскольку многие люди используют нашу библиотеку, важно, чтобы мы не вносили внезапные изменения в код, которые могут потенциально сломать много пользовательского кода в результате, то есть нам нужно, чтобы он был как можно более обратно совместимый насколько возможно, чтобы избежать массовых нарушений.

Pre-commit#

Дополнительно, Непрерывная интеграция будет запускать проверки форматирования кода, такие как black, ruff, isort, и clang-format и более с использованием хуки pre-commit. Любые предупреждения из этих проверок приведут к Непрерывная интеграция к сбою; поэтому полезно запустить проверку самостоятельно перед отправкой кода. Это можно сделать, установив pre-commit (что уже должно было произойти, если вы следовали инструкциям в Настройка среды разработки) и затем запуск:

pre-commit install

из корня репозитория pandas. Теперь все проверки стиля будут выполняться каждый раз при коммите изменений без необходимости запускать каждую вручную. Кроме того, используя pre-commit также позволит вам легче оставаться в курсе наших проверок кода по мере их изменения.

Обратите внимание, что при необходимости можно пропустить эти проверки с помощью git commit --no-verify.

Если вы не хотите использовать pre-commit как часть вашего рабочего процесса, вы всё ещё можете использовать его для запуска проверок одним из следующих способов:

pre-commit run --files <files you have modified>
pre-commit run --from-ref=upstream/main --to-ref=HEAD --all-files

без необходимости выполнения pre-commit install заранее.

Наконец, у нас также есть некоторые медленные проверки pre-commit, которые не запускаются при каждом коммите, но выполняются во время непрерывной интеграции. Вы можете запустить их вручную с помощью:

pre-commit run --hook-stage manual --all-files

Примечание

Возможно, вам потребуется периодически запускать pre-commit gc, чтобы очистить репозитории, которые больше не используются.

Примечание

Если у вас есть конфликтующие установки virtualenv, тогда вы можете получить ошибку - см. здесь.

Также, из-за ошибка в virtualenvвы можете столкнуться с проблемами при использовании conda. Чтобы решить это, вы можете понизить версию virtualenv до версии 20.0.33.

Примечание

Если вы недавно выполнили слияние main из вышестоящей ветки, некоторые из зависимостей, используемых pre-commit могли измениться. Убедитесь, что обновите вашу среду разработки.

Необязательные зависимости#

Необязательные зависимости (например, matplotlib) должны импортироваться с помощью приватного вспомогательного метода pandas.compat._optional.import_optional_dependency. Это обеспечивает согласованное сообщение об ошибке, когда зависимость не выполняется.

Все методы, использующие опциональную зависимость, должны включать тест, проверяющий, что ImportError возникает, когда не найдена опциональная зависимость. Этот тест следует пропустить, если библиотека присутствует.

Все опциональные зависимости должны быть задокументированы в Необязательные зависимости и минимальная требуемая версия должна быть установлена в pandas.compat._optional.VERSIONS словарь.

Обратная совместимость#

Пожалуйста, старайтесь сохранять обратную совместимость. У pandas много пользователей с большим количеством существующего кода, поэтому не нарушайте ее, если это возможно. Если вы считаете, что нарушение необходимо, четко укажите почему в рамках запроса на слияние. Также будьте осторожны при изменении сигнатур методов и добавляйте предупреждения об устаревании, где это необходимо. Также добавьте директиву sphinx для устаревших функций или методов.

Если существует функция с теми же аргументами, что и у устаревшей, вы можете использовать pandas.util._decorators.deprecate:

from pandas.util._decorators import deprecate

deprecate('old_func', 'new_func', '1.1.0')

В противном случае вам нужно сделать это вручную:

import warnings
from pandas.util._exceptions import find_stack_level


def old_func():
    """Summary of the function.

    .. deprecated:: 1.1.0
       Use new_func instead.
    """
    warnings.warn(
        'Use new_func instead.',
        FutureWarning,
        stacklevel=find_stack_level(),
    )
    new_func()


def new_func():
    pass

Вам также потребуется

  1. Написать новый тест, который проверяет выдачу предупреждения при вызове с устаревшим аргументом

  2. Обновить все существующие тесты и код pandas для использования нового аргумента

См. Тестирование предупреждения подробнее.

Подсказки типов#

pandas настоятельно рекомендует использовать PEP 484 подсказки типов стиля. Новая разработка должна содержать подсказки типов, и pull requests для аннотирования существующего кода также принимаются!

Рекомендации по стилю#

Импорты типов должны следовать from typing import ... соглашение. Ваш код может быть автоматически переписан для использования некоторых современных конструкций (например, с использованием встроенного list вместо typing.List) с помощью проверки pre-commit.

В некоторых случаях в кодовой базе классы могут определять переменные класса, которые перекрывают встроенные функции. Это вызывает проблему, описанную в Mypy 1775. Защитное решение здесь — создать однозначный псевдоним встроенной функции и использовать его без аннотации. Например, если вы встретите определение, такое как

class SomeClass1:
    str = None

Соответствующий способ аннотировать это будет следующим

str_type = str

class SomeClass2:
    str: str_type = None

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

from typing import cast

from pandas.core.dtypes.common import is_number

def cannot_infer_bad(obj: Union[str, int, float]):

    if is_number(obj):
        ...
    else:  # Reasonably only str objects would reach this but...
        obj = cast(str, obj)  # Mypy complains without this!
        return obj.upper()

Ограничение здесь в том, что хотя человек может разумно понять, что is_number будет перехватывать int и float типы, которые mypy пока не может вывести (см. mypy #5206. Хотя вышеуказанное работает, использование cast является настоятельно не рекомендуется. Там, где это применимо, предпочтительна рефакторизация кода для удовлетворения статического анализа

def cannot_infer_good(obj: Union[str, int, float]):

    if isinstance(obj, str):
        return obj.upper()
    else:
        ...

С пользовательскими типами и выводом это не всегда возможно, поэтому делаются исключения, но следует приложить все усилия, чтобы избежать cast прежде чем идти по таким путям.

специфичные для pandas типы#

Часто используемые типы, специфичные для pandas, появятся в pandas._typing и вы должны использовать их там, где это применимо. Этот модуль пока является приватным, но в конечном итоге он должен быть доступен сторонним библиотекам, которые хотят реализовать проверку типов для pandas.

Например, довольно много функций в pandas принимают dtype аргумент. Это может быть выражено как строка, например "object", a numpy.dtype как np.int64 или даже pandas ExtensionDtype как pd.CategoricalDtype. Вместо того чтобы обременять пользователя необходимостью постоянно аннотировать все эти опции, это можно просто импортировать и повторно использовать из модуля pandas._typing

from pandas._typing import Dtype

def as_type(dtype: Dtype) -> ...:
    ...

Этот модуль в конечном итоге будет содержать типы для часто используемых концепций, таких как «path-like», «array-like», «numeric» и т.д., а также может содержать псевдонимы для часто встречающихся параметров, таких как axis. Разработка этого модуля активна, поэтому обязательно обратитесь к исходному коду для получения самого актуального списка доступных типов.

Проверка аннотаций типов#

pandas использует mypy и pyright для статического анализа кодовой базы и подсказок типов. После внесения изменений вы можете убедиться в согласованности подсказок типов, запустив

pre-commit run --hook-stage manual --all-files mypy
pre-commit run --hook-stage manual --all-files pyright
pre-commit run --hook-stage manual --all-files pyright_reportGeneralTypeIssues
# the following might fail if the installed pandas version does not correspond to your local git version
pre-commit run --hook-stage manual --all-files stubtest

в вашем окружении Python.

Предупреждение

  • Обратите внимание, что приведённые выше команды используют текущую среду Python. Если ваши пакеты Python старше/новее установленных в CI pandas, эти команды могут завершиться неудачей. Это часто происходит, когда mypy или numpy версии не совпадают. Пожалуйста, смотрите как настроить окружение Python или выбрать недавно успешный рабочий процесс, выберите задание "Docstring validation, typing, and other manual pre-commit hooks", затем нажмите "Set up Conda" и "Environment info", чтобы увидеть, какие версии устанавливает CI pandas.

Тестирование аннотаций типов в коде с использованием pandas#

Предупреждение

  • Pandas пока не является библиотекой py.typed (PEP 561)! Основная цель локального объявления pandas как библиотеки py.typed — тестирование и улучшение встроенных аннотаций типов pandas.

Пока pandas не станет библиотекой py.typed, можно легко экспериментировать с аннотациями типов, поставляемыми с pandas, создав пустой файл с именем 'py.typed' в папке установки pandas:

python -c "import pandas; import pathlib; (pathlib.Path(pandas.__path__[0]) / 'py.typed').touch()"

Наличие файла py.typed сигнализирует проверяющим типам, что pandas уже является библиотекой py.typed. Это делает проверяющие типы осведомленными о аннотациях типов, поставляемых с pandas.

Тестирование с непрерывной интеграцией#

Набор тестов pandas будет запускаться автоматически на GitHub Actions сервисы непрерывной интеграции, после того как ваш pull request будет отправлен. Однако, если вы хотите запустить набор тестов на ветке до отправки pull request, то сервисы непрерывной интеграции должны быть подключены к вашему репозиторию GitHub. Инструкции здесь для GitHub Actions.

Pull-request будет рассмотрен для слияния, когда у вас будет полностью 'зеленая' сборка. Если какие-либо тесты не проходят, то вы получите красный 'X', где можно кликнуть, чтобы увидеть отдельные неудачные тесты. Это пример зеленой сборки.

../_images/ci.png

Разработка через тестирование#

pandas серьезно относится к тестированию и настоятельно рекомендует участникам придерживаться разработка через тестирование (TDD). Этот процесс разработки «основан на повторении очень короткого цикла разработки: сначала разработчик пишет (изначально неудачный) автоматизированный тест, который определяет желаемое улучшение или новую функцию, затем создает минимальное количество кода для прохождения этого теста». Поэтому, прежде чем писать какой-либо код, вы должны написать свои тесты. Часто тест можно взять из исходного вопроса на GitHub. Однако всегда стоит рассмотреть дополнительные случаи использования и написать соответствующие тесты.

Добавление тестов — один из самых частых запросов после отправки кода в pandas. Поэтому стоит привыкнуть писать тесты заранее, чтобы это никогда не было проблемой.

Написание тестов#

Все тесты должны быть помещены в tests подкаталог конкретного пакета. Эта папка содержит множество текущих примеров тестов, и мы предлагаем обращаться к ним для вдохновения.

В качестве общего совета, вы можете использовать функцию поиска в вашей интегрированной среде разработки (IDE) или команду git grep в терминале, чтобы найти тестовые файлы, в которых вызывается метод. Если вы не уверены в лучшем месте для размещения вашего теста, сделайте наилучшее предположение, но учтите, что рецензенты могут попросить переместить тест в другое место.

Чтобы использовать git grep, вы можете выполнить следующую команду в терминале:

git grep "function_name("

Это выполнит поиск по всем файлам в вашем репозитории текста function_name(. Это может быть полезным способом быстро найти функцию в кодовой базе и определить лучшее место для добавления теста.

В идеале должно быть одно и только одно очевидное место для размещения теста. Пока мы не достигнем этого идеала, вот некоторые эмпирические правила, где должен находиться тест.

  1. Зависит ли ваш тест только от кода в pd._libs.tslibs? Этот тест, вероятно, принадлежит одному из:

    • tests.tslibs

      Примечание

      Нет файла в tests.tslibs должен импортировать из любых модулей pandas вне pd._libs.tslibs

    • tests.scalar

    • tests.tseries.offsets

  2. Зависит ли ваш тест только от кода в pd._libs? Этот тест, вероятно, принадлежит одному из:

    • tests.libs

    • tests.groupby.test_libgroupby

  3. Ваш тест для арифметического или сравнительного метода? Этот тест, вероятно, принадлежит одному из:

    • tests.arithmetic

      Примечание

      Они предназначены для тестов, которые можно использовать для проверки поведения DataFrame/Series/Index/ExtensionArray с помощью box_with_array фикстура.

    • tests.frame.test_arithmetic

    • tests.series.test_arithmetic

  4. Ваш тест предназначен для метода редукции (min, max, sum, prod, …)? Этот тест, вероятно, относится к одному из:

    • tests.reductions

      Примечание

      Предназначены для тестов, которые можно использовать совместно для проверки поведения DataFrame/Series/Index/ExtensionArray.

    • tests.frame.test_reductions

    • tests.series.test_reductions

    • tests.test_nanops

  5. Это тест для метода индексирования? Это самый сложный случай для определения, куда относится тест, потому что существует много таких тестов, и многие из них тестируют более одного метода (например, оба Series.__getitem__ и Series.loc.__getitem__)

    1. Тест специально проверяет метод Index (например, Index.get_loc, Index.get_indexer)? Этот тест, вероятно, относится к одной из:

      • tests.indexes.test_indexing

      • tests.indexes.fooindex.test_indexing

      В этих файлах должен быть тестовый класс, специфичный для метода, например, TestGetLoc.

      В большинстве случаев ни Series ни DataFrame объекты должны быть необходимы в этих тестах.

    2. Это тест для метода индексации Series или DataFrame other чем __getitem__ или __setitem__, например, xs, where, take, mask, lookup, или insert? Этот тест, вероятно, принадлежит одному из:

      • tests.frame.indexing.test_methodname

      • tests.series.indexing.test_methodname

    3. Является ли тест для любого из loc, iloc, at, или iat? Этот тест, вероятно, принадлежит одному из:

      • tests.indexing.test_loc

      • GH 19342

      • tests.indexing.test_at

      • tests.indexing.test_iat

      В соответствующем файле тестовые классы соответствуют либо типам индексаторов (например, TestLocBooleanMask) или основные случаи использования (например, TestLocSetitemWithExpansion).

      См. примечание в разделе D) о тестах, которые проверяют несколько методов индексирования.

    4. Является ли тест для Series.__getitem__, Series.__setitem__, DataFrame.__getitem__, или DataFrame.__setitem__? Этот тест, вероятно, принадлежит одному из:

      • tests.series.test_getitem

      • tests.series.test_setitem

      • tests.frame.test_getitem

      • tests.frame.test_setitem

      Во многих случаях такой тест может проверять несколько похожих методов, например.

      import pandas as pd
      import pandas._testing as tm
      
      def test_getitem_listlike_of_ints():
          ser = pd.Series(range(5))
      
          result = ser[[3, 4]]
          expected = pd.Series([2, 3])
          tm.assert_series_equal(result, expected)
      
          result = ser.loc[[3, 4]]
          tm.assert_series_equal(result, expected)
      

    В подобных случаях местоположение теста должно основываться на базовый тестируемого метода. Или в случае теста для исправления ошибки, местоположение фактической ошибки. Таким образом, в этом примере мы знаем, что Series.__getitem__ вызовы Series.loc.__getitem__, так что это действительно тест для loc.__getitem__. Так что этот тест принадлежит tests.indexing.test_loc.

  6. Ваш тест предназначен для метода DataFrame или Series?

    1. Является ли метод методом построения графиков? Этот тест, вероятно, принадлежит одному из:

      • tests.plotting

    2. Является ли метод методом ввода-вывода? Этот тест, вероятно, принадлежит одному из:

      • tests.io

        Примечание

        Это включает to_string но исключает __repr__, что проверяется в tests.frame.test_repr и tests.series.test_repr. Другие классы часто имеют test_formats файл.

    3. В противном случае Этот тест, вероятно, принадлежит одному из:

      • tests.series.methods.test_mymethod

      • tests.frame.methods.test_mymethod

        Примечание

        Если тест может быть общим между DataFrame/Series с использованием frame_or_series фикстура, по соглашению она помещается в tests.frame файл.

  7. Ваш тест для метода Index, не зависящий от Series/DataFrame? Этот тест, вероятно, принадлежит одному из:

    • tests.indexes

  1. Ваш тест для одного из ExtensionArrays, предоставляемых pandas (Categorical, DatetimeArray, TimedeltaArray, PeriodArray, IntervalArray, NumpyExtensionArray, FloatArray, BoolArray, StringArray)? Этот тест, вероятно, относится к одной из:

    • tests.arrays

  2. Это тест для все Подклассы ExtensionArray ("EA Interface")? Этот тест, вероятно, принадлежит одному из:

    • tests.extension

Используя pytest#

Структура теста#

существующая структура тестов pandas в основном основанный на классах, что означает, что тесты обычно находятся внутри класса.

class TestReallyCoolFeature:
    def test_cool_feature_aspect(self):
        pass

Мы предпочитаем более functional стиль с использованием pytest фреймворк, который предлагает более богатую тестовую среду, облегчающую тестирование и разработку. Таким образом, вместо написания тестовых классов мы будем писать тестовые функции следующим образом:

def test_really_cool_feature():
    pass

Предпочтительный pytest идиомы#

  • Функциональные тесты с названием def test_* и only принимают аргументы, которые являются либо фикстурами, либо параметрами.

  • Используйте простой assert для тестирования скаляров и проверки истинности

  • Используйте tm.assert_series_equal(result, expected) и tm.assert_frame_equal(result, expected) для сравнения Series и DataFrame результаты соответственно.

  • Используйте @pytest.mark.parameterize при тестировании нескольких случаев.

  • Используйте pytest.mark.xfail когда тестовый случай ожидается неудачным.

  • Используйте pytest.mark.skip когда тестовый случай никогда не ожидается к прохождению.

  • Используйте pytest.param когда тестовому случаю требуется определённая метка.

  • Используйте @pytest.fixture если несколько тестов могут использовать общий объект настройки.

Предупреждение

Не используйте pytest.xfail (что отличается от pytest.mark.xfail) поскольку он немедленно останавливает тест и не проверяет, завершится ли тест неудачей. Если это желаемое поведение, используйте pytest.skip вместо этого.

Если известно, что тест не проходит, но способ, которым он не проходит, не предназначен для захвата, используйте pytest.mark.xfail Обычно этот метод используется для теста, который проявляет ошибочное поведение или нереализованную функцию. Если падающий тест имеет нестабильное поведение, используйте аргумент strict=False. Это сделает так, что pytest не завершится неудачей, если тест случайно пройден. Использование strict=False крайне нежелательно, используйте его только в крайнем случае.

Предпочтительнее использовать декоратор @pytest.mark.xfail и аргумент pytest.param по использованию в тесте, чтобы тест был соответствующим образом помечен во время фазы сбора pytest. Для xfail теста, который включает несколько параметров, фикстуру или их комбинацию, возможно xfail только во время фазы тестирования. Для этого используйте request фикстура:

def test_xfail(request):
    mark = pytest.mark.xfail(raises=TypeError, reason="Indicate why here")
    request.applymarker(mark)

xfail не следует использовать для тестов, связанных с ошибками из-за неверных аргументов пользователя. Для этих тестов необходимо проверить правильность типа исключения и сообщения об ошибке, используя pytest.raises вместо этого.

Тестирование предупреждения#

Используйте tm.assert_produces_warning в качестве контекстного менеджера для проверки, что блок кода вызывает предупреждение.

with tm.assert_produces_warning(DeprecationWarning):
    pd.deprecated_function()

Если предупреждение не должно происходить в блоке кода, передайте False в контекстный менеджер.

with tm.assert_produces_warning(False):
    pd.no_warning_function()

Если у вас есть тест, который должен выдавать предупреждение, но вы фактически не тестируете само предупреждение (например, потому что оно будет удалено в будущем, или потому что мы соответствуем поведению сторонней библиотеки), тогда используйте pytest.mark.filterwarnings чтобы игнорировать ошибку.

@pytest.mark.filterwarnings("ignore:msg:category")
def test_thing(self):
    pass

Тестирование исключения#

Используйте pytest.raises в качестве контекстного менеджера с конкретным подклассом исключения (т.е. никогда не используйте Exception) и сообщение об исключении в match.

with pytest.raises(ValueError, match="an error"):
    raise ValueError("an error")

Тестирование с участием файлов#

The tm.ensure_clean контекстный менеджер создает временный файл для тестирования, с сгенерированным именем файла (или вашим именем файла, если предоставлено), который автоматически удаляется при выходе из блока контекста.

with tm.ensure_clean('my_file_path') as path:
    # do something with the path

Тестирование, связанное с сетевым подключением#

Модульный тест не должен обращаться к общедоступному набору данных через интернет из-за ненадежности сетевых подключений и отсутствия владения сервером, к которому происходит подключение. Для имитации этого взаимодействия используйте httpserver фикстура из плагин pytest-localserver. с синтетическими данными.

@pytest.mark.network
@pytest.mark.single_cpu
def test_network(httpserver):
    httpserver.serve_content(content="content")
    result = pd.read_html(httpserver.url)

Пример#

Вот пример автономного набора тестов в файле pandas/tests/test_cool_feature.py которые иллюстрируют несколько функций, которые мы предпочитаем использовать. Пожалуйста, не забудьте добавить номер GitHub Issue в качестве комментария к новому тесту.

import pytest
import numpy as np
import pandas as pd


@pytest.mark.parametrize('dtype', ['int8', 'int16', 'int32', 'int64'])
def test_dtypes(dtype):
    assert str(np.dtype(dtype)) == dtype


@pytest.mark.parametrize(
    'dtype', ['float32', pytest.param('int16', marks=pytest.mark.skip),
              pytest.param('int32', marks=pytest.mark.xfail(
                  reason='to show how it works'))])
def test_mark(dtype):
    assert str(np.dtype(dtype)) == 'float32'


@pytest.fixture
def series():
    return pd.Series([1, 2, 3])


@pytest.fixture(params=['int8', 'int16', 'int32', 'int64'])
def dtype(request):
    return request.param


def test_series(series, dtype):
    # GH 
    result = series.astype(dtype)
    assert result.dtype == dtype

    expected = pd.Series([1, 2, 3], dtype=dtype)
    tm.assert_series_equal(result, expected)

Тестовый запуск этого дает

((pandas) bash-3.2$ pytest  test_cool_feature.py  -v
=========================== test session starts ===========================
platform darwin -- Python 3.6.2, pytest-3.6.0, py-1.4.31, pluggy-0.4.0
collected 11 items

tester.py::test_dtypes[int8] PASSED
tester.py::test_dtypes[int16] PASSED
tester.py::test_dtypes[int32] PASSED
tester.py::test_dtypes[int64] PASSED
tester.py::test_mark[float32] PASSED
tester.py::test_mark[int16] SKIPPED
tester.py::test_mark[int32] xfail
tester.py::test_series[int8] PASSED
tester.py::test_series[int16] PASSED
tester.py::test_series[int32] PASSED
tester.py::test_series[int64] PASSED

Тесты, которые у нас есть parametrized теперь доступны через имя теста, например, мы можем запустить их с -k int8 для выбора подмножества only те тесты, которые соответствуют int8.

((pandas) bash-3.2$ pytest  test_cool_feature.py  -v -k int8
=========================== test session starts ===========================
platform darwin -- Python 3.6.2, pytest-3.6.0, py-1.4.31, pluggy-0.4.0
collected 11 items

test_cool_feature.py::test_dtypes[int8] PASSED
test_cool_feature.py::test_series[int8] PASSED

Используя hypothesis#

Hypothesis — это библиотека для тестирования на основе свойств. Вместо явного параметризации теста вы можете описать все допустимые входные данные и позволить Hypothesis попытаться найти неудачный ввод. Еще лучше, независимо от того, сколько случайных примеров он пробует, Hypothesis всегда сообщает о единственном минимальном контрпримере для ваших утверждений - часто о примере, который вы никогда бы не подумали протестировать.

См. Начало работы с Hypothesis для более подробного введения, затем обратитесь к документации Hypothesis для подробностей.

import json
from hypothesis import given, strategies as st

any_json_value = st.deferred(lambda: st.one_of(
    st.none(), st.booleans(), st.floats(allow_nan=False), st.text(),
    st.lists(any_json_value), st.dictionaries(st.text(), any_json_value)
))


@given(value=any_json_value)
def test_json_roundtrip(value):
    result = json.loads(json.dumps(value))
    assert value == result

Этот тест демонстрирует несколько полезных функций Hypothesis, а также показывает хороший пример использования: проверка свойств, которые должны выполняться на большом или сложном домене входных данных.

Чтобы набор тестов pandas работал быстро, параметризованные тесты предпочтительны, если входные данные или логика просты, а тесты Hypothesis оставлены для случаев со сложной логикой или когда есть слишком много комбинаций опций или тонких взаимодействий для тестирования (или обдумывания!) всех из них.

Запуск набора тестов#

Тесты можно затем запустить непосредственно в вашем клоне Git (без необходимости устанавливать pandas), набрав:

pytest pandas

Примечание

Если несколько тестов не проходят, это может быть не связано с установкой pandas. Некоторые тесты (например, некоторые SQLAlchemy) требуют дополнительной настройки, другие могут начать проваливаться из-за выхода новой версии незакрепленной библиотеки, а некоторые могут быть нестабильными при параллельном запуске. Пока вы можете импортировать pandas из локально собранной версии, ваша установка, вероятно, в порядке, и вы можете начать вносить вклад!

Часто стоит сначала запустить только подмножество тестов вокруг ваших изменений перед запуском всей тестовой батареи (совет: вы можете использовать приложение pandas-coverage) чтобы узнать, какие тесты затрагивают строки кода, которые вы изменили, и затем запустить только их).

Самый простой способ сделать это:

pytest pandas/path/to/test.py -k regex_matching_test_name

Или с одной из следующих конструкций:

pytest pandas/tests/[test-module].py
pytest pandas/tests/[test-module].py::[TestClass]
pytest pandas/tests/[test-module].py::[TestClass]::[test_method]

Используя pytest-xdist, который включен в нашу среду 'pandas-dev', можно ускорить локальное тестирование на многоядерных машинах. -n флаг number может быть указан при запуске pytest для параллельного запуска тестов на указанном количестве ядер или auto для использования всех доступных ядер на вашем компьютере.

# Utilize 4 cores
pytest -n 4 pandas

# Utilizes all available cores
pytest -n auto pandas

Если вы хотите ускорить процесс дальше, более продвинутое использование этой команды будет выглядеть так

pytest pandas -n 4 -m "not slow and not network and not db and not single_cpu" -r sxX

В дополнение к увеличению производительности при многопоточности это улучшает скорость тестирования, пропуская некоторые тесты с использованием -m флаг отметки:

  • медленный: любой тест, занимающий много времени (думайте о секундах, а не миллисекундах)

  • network: тесты, требующие подключения к сети

  • db: тесты, требующие базы данных (mysql или postgres)

  • single_cpu: тесты, которые должны выполняться только на одном процессоре

Возможно, вам стоит включить следующую опцию, если она актуальна для вас:

  • arm_slow: любой тест, занимающий много времени на архитектуре arm64

Эти маркеры определены в этом файле toml , под [tool.pytest.ini_options] в списке с именем markers, на случай, если вы хотите проверить, были ли созданы новые, представляющие для вас интерес.

The -r флаг report будет отображать краткую сводную информацию (см. документация pytest). Здесь мы отображаем количество:

  • s: пропущенные тесты

  • x: проваленные тесты

  • X: пройденные тесты

Сводка необязательна и может быть удалена, если вам не нужна дополнительная информация. Использование опции параллелизации может значительно сократить время локального запуска тестов перед отправкой запроса на включение изменений.

Если вам требуется помощь с результатами, что случалось в прошлом, пожалуйста, установите сид перед запуском команды и открытием отчета об ошибке, чтобы мы могли воспроизвести её. Вот пример установки сида на Windows

set PYTHONHASHSEED=314159265
pytest pandas -n 4 -m "not slow and not network and not db and not single_cpu" -r sxX

В Unix используйте

export PYTHONHASHSEED=314159265
pytest pandas -n 4 -m "not slow and not network and not db and not single_cpu" -r sxX

Для получения дополнительной информации см. pytest документация.

Кроме того, можно запустить

pd.test()

с импортированным pandas для запуска тестов аналогично.

Запуск набора тестов производительности#

Производительность имеет значение, и стоит подумать, не привнес ли ваш код регрессии производительности. pandas находится в процессе миграции к asv benchmarks для удобного мониторинга производительности критических операций pandas. Все эти тесты производительности находятся в pandas/asv_bench каталог, и результаты тестов можно найти здесь.

Чтобы использовать все возможности asv, вам потребуется либо conda или virtualenvДля получения дополнительной информации, пожалуйста, ознакомьтесь с страница установки asv .

Для установки asv:

pip install git+https://github.com/airspeed-velocity/asv

Если вам нужно запустить бенчмарк, измените директорию на asv_bench/ и запустить:

asv continuous -f 1.1 upstream/main HEAD

Вы можете заменить HEAD с именем ветки, над которой вы работаете, и сообщите о тестах производительности, изменившихся более чем на 10%. Команда использует conda по умолчанию для создания тестовых окружений. Если вы хотите использовать virtualenv, напишите:

asv continuous -f 1.1 -E virtualenv upstream/main HEAD

The -E virtualenv опция должна быть добавлена ко всем asv команды для запуска тестов производительности. Значение по умолчанию определено в asv.conf.json.

Запуск полного набора тестов производительности может занять целый день, в зависимости от вашего оборудования и его загрузки. Однако обычно достаточно вставить в pull request только часть результатов, чтобы показать, что внесённые изменения не вызывают неожиданного снижения производительности. Вы можете запустить конкретные тесты производительности с помощью -b флаг, который принимает регулярное выражение. Например, это будет запускать только бенчмарки из pandas/asv_bench/benchmarks/groupby.py файл:

asv continuous -f 1.1 upstream/main HEAD -b ^groupby

Если вы хотите запустить только определенную группу тестов из файла, вы можете сделать это используя . в качестве разделителя. Например:

asv continuous -f 1.1 upstream/main HEAD -b groupby.GroupByMethods

будет запускать только GroupByMethods бенчмарк, определенный в groupby.py.

Вы также можете запустить набор тестов производительности, используя версию pandas уже установлен в вашей текущей среде Python. Это может быть полезно, если у вас нет virtualenv или conda, или вы используете setup.py develop подход, обсуждаемый выше; для сборки на месте вам нужно установить PYTHONPATH, например, PYTHONPATH="$PWD/.." asv [remaining arguments]. Вы можете запустить тесты производительности, используя существующую среду Python, следующим образом:

asv run -e -E existing

или, чтобы использовать определённый интерпретатор Python,:

asv run -e -E existing:python3.6

Это отобразит stderr из бенчмарков и использует ваш локальный python который исходит от вашего $PATH.

Информация о том, как написать тест производительности и как использовать asv, можно найти в документация asv.

Документирование вашего кода#

Изменения должны быть отражены в примечаниях к выпуску, расположенных в doc/source/whatsnew/vx.y.z.rst. Этот файл содержит текущий журнал изменений для каждого выпуска. Добавьте запись в этот файл, чтобы задокументировать ваше исправление, улучшение или (неизбежное) критическое изменение. Убедитесь, что включили номер проблемы GitHub при добавлении вашей записи (используя :issue:`1234` где 1234 это номер проблемы/запроса на включение). Ваша запись должна быть написана полными предложениями с правильной грамматикой.

При упоминании частей API используйте Sphinx :func:, :meth:, или :class: директива по мере необходимости. Не все публичные функции и методы API имеют страницу документации; в идеале ссылки добавляются только если они работают. Обычно можно найти похожие примеры, проверив заметки о выпуске одной из предыдущих версий.

Если ваш код исправляет ошибку, добавьте свою запись в соответствующий раздел исправлений ошибок. Избегайте добавления в Other раздел; только в редких случаях записи должны попадать туда. Будучи максимально краткими, описание ошибки должно включать, как пользователь может столкнуться с ней, и указание на саму ошибку, например, «выдает неверные результаты» или «ошибочно вызывает». Также может быть необходимо указать новое поведение.

Если ваш код является улучшением, скорее всего, необходимо добавить примеры использования в существующую документацию. Это можно сделать, следуя разделу, касающемуся документация. Кроме того, чтобы пользователи знали, когда была добавлена эта функция, versionadded директива используется. Синтаксис sphinx для этого:

.. versionadded:: 2.1.0

Это поместит текст Новое в версии 2.1.0 где бы вы ни разместили директиву sphinx. Это также следует поместить в строку документации при добавлении новой функции или метода (пример) или новый аргумент ключевого слова (пример).