Выполнение unittest с типичной структурой тестового каталога
Очень распространенная структура каталогов даже для простого модуля Python, по-видимому, заключается в разделении модульных тестов в их собственный каталог test
:
new_project/
antigravity/
antigravity.py
test/
test_antigravity.py
setup.py
etc.
например, смотрите проект проекта Python.
Мой вопрос просто Какой обычный способ фактического запуска тестов? Я подозреваю, что это очевидно для всех, кроме меня, но вы не можете просто запустить python test_antigravity.py
из тестового каталога как его import antigravity
завершится с ошибкой, так как модуль не находится на пути.
Я знаю, что могу изменить PYTHONPATH и другие трюки, связанные с поисковым путем, но я не могу поверить, что самый простой способ - это хорошо, если вы разработчик, но не реалистично ожидать, что ваши пользователи будут использовать, если они просто хотят проверить тесты проходят.
Другой альтернативой является просто копирование тестового файла в другой каталог, но он кажется немного глупым и не учитывает, что он должен быть в отдельном каталоге, чтобы начать с.
Итак, если вы только что загрузили источник в мой новый проект, как бы вы запускали модульные тесты? Я бы предпочел ответ, который позволил бы мне сказать моим пользователям: "Для запуска модульных тестов сделайте X".
Ответы
Ответ 1
Лучшим решением, на мой взгляд, является использование интерфейса командной строки unittest
, который добавит каталог в sys.path
, чтобы вы не нужно (сделано в классе TestLoader
).
Например, для такой структуры каталогов:
new_project
├── antigravity.py
└── test_antigravity.py
Вы можете просто запустить:
$ cd new_project
$ python -m unittest test_antigravity
Для структуры каталогов, такой как ваш:
new_project
├── antigravity
│ ├── __init__.py # make it a package
│ └── antigravity.py
└── test
├── __init__.py # also make test a package
└── test_antigravity.py
И в тестовых модулях внутри пакета test
вы можете импортировать пакет antigravity
и его модули как обычно:
# import the package
import antigravity
# import the antigravity module
from antigravity import antigravity
# or an object inside the antigravity module
from antigravity.antigravity import my_object
Запуск одного тестового модуля:
Для запуска одного тестового модуля в этом случае test_antigravity.py
:
$ cd new_project
$ python -m unittest test.test_antigravity
Просто отправьте тестовый модуль так же, как вы его импортируете.
Запуск одного тестового примера или тестового метода:
Также вы можете запустить одиночный TestCase
или один метод тестирования:
$ python -m unittest test.test_antigravity.GravityTestCase
$ python -m unittest test.test_antigravity.GravityTestCase.test_method
Выполнение всех тестов:
Вы также можете использовать тестовое обнаружение, которое обнаружит и проведет все тесты для вас, они должны быть модулями или пакетами с именем test*.py
( может быть изменен с помощью флага -p, --pattern
):
$ cd new_project
$ python -m unittest discover
Это приведет к запуску всех модулей test*.py
внутри пакета test
.
Ответ 2
Самым простым решением для ваших пользователей является предоставление исполняемого скрипта (runtests.py
или чего-то подобного), который загружает необходимую тестовую среду, включая, при необходимости, временное добавление корневого каталога проекта в sys.path
. Это не требует, чтобы пользователи устанавливали переменные среды, что-то вроде этого прекрасно работает в скрипте начальной загрузки:
import sys, os
sys.path.insert(0, os.path.dirname(__file__))
Тогда ваши инструкции для пользователей могут быть такими же простыми, как "python runtests.py
".
Конечно, если вам действительно нужен путь os.path.dirname(__file__)
, то вам вообще не нужно добавлять его в sys.path
; Python всегда помещает каталог запущенного в данный момент скрипта в начало sys.path
, поэтому, в зависимости от вашей структуры каталогов, достаточно просто найти runtests.py
в нужном месте.
Кроме того, модуль unittest в Python 2. 7+ (который перенесен как unittest2 для Python 2.6 и более ранних версий) теперь имеет встроенное тестовое обнаружение, поэтому вам больше не нужен нос, если вам нужно автоматическое обнаружение тестов: ваши пользовательские инструкции могут быть такими же простыми, как python -m unittest discover
.
Ответ 3
Обычно я создаю "run tests" script в каталоге проекта (тот, который является общим как для исходного каталога, так и для test
), который загружает набор "Все тесты". Обычно это шаблонный код, поэтому я могу повторно использовать его из проекта в проект.
run_tests.py:
import unittest
import test.all_tests
testSuite = test.all_tests.create_test_suite()
text_runner = unittest.TextTestRunner().run(testSuite)
test/all_tests.py(from Как выполнить все модульные тесты Python в каталоге?)
import glob
import unittest
def create_test_suite():
test_file_strings = glob.glob('test/test_*.py')
module_strings = ['test.'+str[5:len(str)-3] for str in test_file_strings]
suites = [unittest.defaultTestLoader.loadTestsFromName(name) \
for name in module_strings]
testSuite = unittest.TestSuite(suites)
return testSuite
С помощью этой настройки вы можете просто include antigravity
в своих тестовых модулях. Недостатком является то, что вам понадобится дополнительный код поддержки для выполнения конкретного теста... Я просто запускаю их каждый раз.
Ответ 4
Из статьи, на которую вы ссылаетесь:
Создайте файл test_modulename.py и поместите в него свои юнит-тесты. Поскольку тестовые модули находятся в отдельном каталоге от вашего кода, вам может потребоваться добавить родительский каталог ваших модулей в PYTHONPATH, чтобы запустить их:
$ cd /path/to/googlemaps
$ export PYTHONPATH=$PYTHONPATH:/path/to/googlemaps/googlemaps
$ python test/test_googlemaps.py
Наконец, есть еще одна популярная среда модульного тестирования для Python (это так важно!), Нос. Нос помогает упростить и расширить встроенную инфраструктуру юнит-тестов (например, он может автоматически найти ваш тестовый код и настроить для вас PYTHONPATH), но он не включен в стандартный дистрибутив Python.
Возможно, вы должны смотреть на нос, как он предлагает?
Ответ 5
если вы запустите "python setup.py development", то пакет будет в пути. Но вы можете не захотеть этого делать, потому что вы можете заразить вашу системную установку python, поэтому существуют такие инструменты, как virtualenv и buildout.
Ответ 6
У меня была такая же проблема, с отдельной папкой юнит-тестов. Из упомянутых предложений я добавляю абсолютный исходный путь к sys.path
.
Преимущество следующего решения заключается в том, что можно запустить файл test/test_yourmodule.py
не меняя сначала его в каталоге test:
import sys, os
testdir = os.path.dirname(__file__)
srcdir = '../antigravity'
sys.path.insert(0, os.path.abspath(os.path.join(testdir, srcdir)))
import antigravity
import unittest
Ответ 7
Используйте setup.py develop
, чтобы ваш рабочий каталог был частью установленной среды Python, затем запустите тесты.
Ответ 8
Если вы используете VS Code, и ваши тесты расположены на том же уровне, что и ваш проект, тогда запуск и отладка вашего кода не работает из коробки. Что вы можете сделать, это изменить файл launch.json:
{
"version": "0.2.0",
"configurations": [
{
"name": "Python",
"type": "python",
"request": "launch",
"stopOnEntry": false,
"pythonPath": "${config:python.pythonPath}",
"program": "${file}",
"cwd": "${workspaceRoot}",
"env": {},
"envFile": "${workspaceRoot}/.env",
"debugOptions": [
"WaitOnAbnormalExit",
"WaitOnNormalExit",
"RedirectOutput"
]
}
]
}
Ключевая строка здесь envFile
"envFile": "${workspaceRoot}/.env",
В корне вашего проекта добавьте файл .env
Внутри вашего файла .env добавьте путь к корню вашего проекта. Это временно добавит
PYTHONPATH = C:\ВАШЕГО\PYTHON\PROJECT\ROOT_DIRECTORY
для вашего проекта, и вы сможете использовать тесты отладки модуля VS Code
Ответ 9
Решение/пример для модуля стирания Python
Учитывая следующую структуру проекта:
ProjectName
├── project_name
| ├── models
| | └── thing_1.py
| └── __main__.py
└── test
├── models
| └── test_thing_1.py
└── __main__.py
Вы можете запустить проект из корневого каталога с помощью python project_name
, который вызывает ProjectName/project_name/__main__.py
.
Чтобы запустить тесты с помощью python test
, эффективно выполнив ProjectName/test/__main__.py
, вам необходимо сделать следующее:
1) Поверните каталог test/models
в пакет, добавив файл __init__.py
. Это делает тестовые примеры внутри вспомогательной директории доступными из родительского каталога test
.
# ProjectName/test/models/__init__.py
from .test_thing_1 import Thing1TestCase
2) Измените свой системный путь в test/__main__.py
, чтобы включить каталог project_name
.
# ProjectName/test/__main__.py
import sys
import unittest
sys.path.append('../project_name')
loader = unittest.TestLoader()
testSuite = loader.discover('test')
testRunner = unittest.TextTestRunner(verbosity=2)
testRunner.run(testSuite)
Теперь вы можете успешно импортировать вещи из project_name
в свои тесты.
# ProjectName/test/models/test_thing_1.py
import unittest
from project_name.models import Thing1 # this doesn't work without 'sys.path.append' per step 2 above
class Thing1TestCase(unittest.TestCase):
def test_thing_1_init(self):
thing_id = 'ABC'
thing1 = Thing1(thing_id)
self.assertEqual(thing_id, thing.id)
Ответ 10
Я заметил, что если вы запустите интерфейс командной строки unittest из своего каталога "src", тогда импорт будет работать без изменений.
python -m unittest discover -s ../test
Если вы хотите поместить это в пакетный файл в каталог проекта, вы можете сделать это:
setlocal & cd src & python -m unittest discover -s ../test
Ответ 11
Можно использовать оболочку, которая запускает выбранные или все тесты.
Например:
./run_tests antigravity/*.py
или для выполнения всех тестов рекурсивно используйте globbing (tests/**/*.py
) (активируйте с помощью shopt -s globstar
).
Оболочка может в основном использовать argparse
для анализа таких аргументов, как:
parser = argparse.ArgumentParser()
parser.add_argument('files', nargs='*')
Затем загрузите все тесты:
for filename in args.files:
exec(open(filename).read())
затем добавьте их в свой тестовый пакет (используя inspect
):
alltests = unittest.TestSuite()
for name, obj in inspect.getmembers(sys.modules[__name__]):
if inspect.isclass(obj) and name.startswith("FooTest"):
alltests.addTest(unittest.makeSuite(obj))
и запустите их:
result = unittest.TextTestRunner(verbosity=2).run(alltests)
Подробнее о этом подробнее.
Смотрите также: Как запустить все тестовые тесты Python в каталоге?
Ответ 12
Ниже приведена моя структура проекта:
ProjectFolder:
- project:
- __init__.py
- item.py
- tests:
- test_item.py
Мне было проще импортировать метод setUp():
import unittest
import sys
class ItemTest(unittest.TestCase):
def setUp(self):
sys.path.insert(0, "../project")
from project import item
# further setup using this import
def test_item_props(self):
# do my assertions
if __name__ == "__main__":
unittest.main()
Ответ 13
Какой обычный способ выполнения тестов
Я использую Python 3.6.2
cd new_project
pytest test/test_antigravity.py
Чтобы установить pytest: sudo pip install pytest
Я не задал никакой переменной пути, и мой импорт не прерывается с той же "тестовой" структурой проекта.
Я прокомментировал этот материал: if __name__ == '__main__'
вот так:
test_antigravity.py
import antigravity
class TestAntigravity(unittest.TestCase):
def test_something(self):
# ... test stuff here
# if __name__ == '__main__':
#
# if __package__ is None:
#
# import something
# sys.path.append(path.dirname(path.dirname(path.abspath(__file__))))
# from .. import antigravity
#
# else:
#
# from .. import antigravity
#
# unittest.main()
Ответ 14
Python 3+
Добавление в @Pierre
Используя unittest
каталогов unittest
следующим образом:
new_project
├── antigravity
│ ├── __init__.py # make it a package
│ └── antigravity.py
└── test
├── __init__.py # also make test a package
└── test_antigravity.py
Чтобы запустить тестовый модуль test_antigravity.py
:
$ cd new_project
$ python -m unittest test.test_antigravity
Или один TestCase
$ python -m unittest test.test_antigravity.GravityTestCase
Обязательно не забывайте __init__.py
даже если пустое значение не будет работать.
Ответ 15
Вы не можете импортировать из родительского каталога без некоторого вуду. Здесь еще один способ, который работает по крайней мере с Python 3.6.
Сначала создайте файл test/context.py со следующим содержимым:
import sys
import os
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
Затем выполните следующий импорт в файл test/test_antigravity.py:
import unittest
try:
import context
except ModuleNotFoundError:
import test.context
import antigravity
Обратите внимание на то, что причина этого предложения о том, что
- сбой импорта test.context при запуске с "python test_antigravity.py" и
- Сбой импорта контекста при запуске с "python -m unittest" из каталога new_project.
С этим обманом они оба работают.
Теперь вы можете запустить все тестовые файлы в тестовой директории с помощью:
$ pwd
/projects/new_project
$ python -m unittest
или запустите отдельный тестовый файл с:
$ cd test
$ python test_antigravity
Хорошо, это не намного красивее, чем содержание context.py в test_antigravity.py, но, возможно, немного. Предложения приветствуются.
Ответ 16
Этот скрипт BASH будет выполнять тестовый каталог python unittest из любой точки файловой системы, независимо от того, в каком рабочем каталоге вы находитесь.
Это полезно, когда вы остаетесь в рабочем каталоге ./src
или ./example
и вам нужен быстрый юнит-тест:
#!/bin/bash
this_program="$0"
dirname="'dirname $this_program'"
readlink="'readlink -e $dirname'"
python -m unittest discover -s "$readlink"/test -v
Нет необходимости в test/__init__.py
файле test/__init__.py
чтобы нагружать ваш пакет/накладные расходы памяти во время производства.
Ответ 17
Вы действительно должны использовать инструмент pip.
Используйте pip install -e .
для установки вашего пакета в режиме разработки. Это очень хорошая практика, рекомендованная pytest (см. их документацию по хорошей практике, где также можно найти два макета проекта, которым нужно следовать).
Ответ 18
Если у вас есть несколько каталогов в вашем тестовом каталоге, то вы должны добавить в каждый каталог файл __init__.py
.
/home/johndoe/snakeoil
└── test
├── __init__.py
└── frontend
└── __init__.py
└── test_foo.py
└── backend
└── __init__.py
└── test_bar.py
Затем, чтобы запустить каждый тест сразу, запустите:
python -m unittest discover -s /home/johndoe/snakeoil/test -t /home/johndoe/snakeoil
Источник: python -m unittest -h
-s START, --start-directory START
Directory to start discovery ('.' default)
-t TOP, --top-level-directory TOP
Top level directory of project (defaults to start
directory)
Ответ 19
Если вы ищете решение только для командной строки:
На основе следующей структуры каталогов (обобщенной с выделенным каталогом источника):
new_project/
src/
antigravity.py
test/
test_antigravity.py
Windows: (в new_project
)
$ set PYTHONPATH=%PYTHONPATH%;%cd%\src
$ python -m unittest discover -s test
Посмотрите этот вопрос, если вы хотите использовать его в цикле для пакета.
Linux: (в new_project
)
$ export PYTHONPATH=$PYTHONPATH:$(pwd)/src [I think - please edit this answer if you are a Linux user and you know this]
$ python -m unittest discover -s test
При таком подходе также возможно добавить дополнительные каталоги в PYTHONPATH, если это необходимо.
Ответ 20
Таким образом, вы сможете запускать тестовые сценарии из любого места, не тратя время на системные переменные из командной строки.
Это добавляет основную папку проекта к пути Python, причем местоположение определяется относительно самого сценария, а не относительно текущего рабочего каталога.
import sys, os
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.realpath(__file__))))
Добавьте это в начало всех ваших тестовых сценариев. Это добавит основную папку проекта в системный путь, поэтому любой импорт модулей, который работает оттуда, теперь будет работать. И не важно, откуда вы запускаете тесты.
Очевидно, вы можете изменить файл project_path_hack, чтобы он соответствовал местоположению вашей основной папки проекта.