Вклад в кодовую базу#
Содержание:
Стандарты кода#
Написание хорошего кода — это не только о том, что вы пишете. Это также о 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
Вам также потребуется
Написать новый тест, который проверяет выдачу предупреждения при вызове с устаревшим аргументом
Обновить все существующие тесты и код 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', где можно кликнуть, чтобы увидеть отдельные неудачные тесты. Это пример зеленой сборки.
Разработка через тестирование#
pandas серьезно относится к тестированию и настоятельно рекомендует участникам придерживаться разработка через тестирование (TDD). Этот процесс разработки «основан на повторении очень короткого цикла разработки: сначала разработчик пишет (изначально неудачный) автоматизированный тест, который определяет желаемое улучшение или новую функцию, затем создает минимальное количество кода для прохождения этого теста». Поэтому, прежде чем писать какой-либо код, вы должны написать свои тесты. Часто тест можно взять из исходного вопроса на GitHub. Однако всегда стоит рассмотреть дополнительные случаи использования и написать соответствующие тесты.
Добавление тестов — один из самых частых запросов после отправки кода в pandas. Поэтому стоит привыкнуть писать тесты заранее, чтобы это никогда не было проблемой.
Написание тестов#
Все тесты должны быть помещены в tests подкаталог конкретного пакета.
Эта папка содержит множество текущих примеров тестов, и мы предлагаем обращаться к ним для
вдохновения.
В качестве общего совета, вы можете использовать функцию поиска в вашей интегрированной среде разработки (IDE) или команду git grep в терминале, чтобы найти тестовые файлы, в которых вызывается метод. Если вы не уверены в лучшем месте для размещения вашего теста, сделайте наилучшее предположение, но учтите, что рецензенты могут попросить переместить тест в другое место.
Чтобы использовать git grep, вы можете выполнить следующую команду в терминале:
git grep "function_name("
Это выполнит поиск по всем файлам в вашем репозитории текста function_name(.
Это может быть полезным способом быстро найти функцию в
кодовой базе и определить лучшее место для добавления теста.
В идеале должно быть одно и только одно очевидное место для размещения теста. Пока мы не достигнем этого идеала, вот некоторые эмпирические правила, где должен находиться тест.
Зависит ли ваш тест только от кода в
pd._libs.tslibs? Этот тест, вероятно, принадлежит одному из:tests.tslibs
Примечание
Нет файла в
tests.tslibsдолжен импортировать из любых модулей pandas внеpd._libs.tslibstests.scalar
tests.tseries.offsets
Зависит ли ваш тест только от кода в pd._libs? Этот тест, вероятно, принадлежит одному из:
tests.libs
tests.groupby.test_libgroupby
Ваш тест для арифметического или сравнительного метода? Этот тест, вероятно, принадлежит одному из:
tests.arithmetic
Примечание
Они предназначены для тестов, которые можно использовать для проверки поведения DataFrame/Series/Index/ExtensionArray с помощью
box_with_arrayфикстура.tests.frame.test_arithmetic
tests.series.test_arithmetic
Ваш тест предназначен для метода редукции (min, max, sum, prod, …)? Этот тест, вероятно, относится к одному из:
tests.reductions
Примечание
Предназначены для тестов, которые можно использовать совместно для проверки поведения DataFrame/Series/Index/ExtensionArray.
tests.frame.test_reductions
tests.series.test_reductions
tests.test_nanops
Это тест для метода индексирования? Это самый сложный случай для определения, куда относится тест, потому что существует много таких тестов, и многие из них тестируют более одного метода (например, оба
Series.__getitem__иSeries.loc.__getitem__)Тест специально проверяет метод Index (например,
Index.get_loc,Index.get_indexer)? Этот тест, вероятно, относится к одной из:tests.indexes.test_indexing
tests.indexes.fooindex.test_indexing
В этих файлах должен быть тестовый класс, специфичный для метода, например,
TestGetLoc.В большинстве случаев ни
SeriesниDataFrameобъекты должны быть необходимы в этих тестах.Это тест для метода индексации Series или DataFrame other чем
__getitem__или__setitem__, например,xs,where,take,mask,lookup, илиinsert? Этот тест, вероятно, принадлежит одному из:tests.frame.indexing.test_methodname
tests.series.indexing.test_methodname
Является ли тест для любого из
loc,iloc,at, илиiat? Этот тест, вероятно, принадлежит одному из:tests.indexing.test_loc
GH 19342
tests.indexing.test_at
tests.indexing.test_iat
В соответствующем файле тестовые классы соответствуют либо типам индексаторов (например,
TestLocBooleanMask) или основные случаи использования (например,TestLocSetitemWithExpansion).См. примечание в разделе D) о тестах, которые проверяют несколько методов индексирования.
Является ли тест для
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.Ваш тест предназначен для метода DataFrame или Series?
Является ли метод методом построения графиков? Этот тест, вероятно, принадлежит одному из:
tests.plotting
Является ли метод методом ввода-вывода? Этот тест, вероятно, принадлежит одному из:
tests.io
Примечание
Это включает
to_stringно исключает__repr__, что проверяется вtests.frame.test_reprиtests.series.test_repr. Другие классы часто имеютtest_formatsфайл.
В противном случае Этот тест, вероятно, принадлежит одному из:
tests.series.methods.test_mymethod
tests.frame.methods.test_mymethod
Примечание
Если тест может быть общим между DataFrame/Series с использованием
frame_or_seriesфикстура, по соглашению она помещается вtests.frameфайл.
Ваш тест для метода Index, не зависящий от Series/DataFrame? Этот тест, вероятно, принадлежит одному из:
tests.indexes
Ваш тест для одного из ExtensionArrays, предоставляемых pandas (
Categorical,DatetimeArray,TimedeltaArray,PeriodArray,IntervalArray,NumpyExtensionArray,FloatArray,BoolArray,StringArray)? Этот тест, вероятно, относится к одной из:tests.arrays
Это тест для все Подклассы 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. Это также следует поместить в строку документации при добавлении новой функции или метода (пример) или новый аргумент ключевого слова (пример).