Запуск unittest с типичной структурой тестовых каталогов
Самая распространенная структура каталогов даже для простого модуля Python, по-видимому, заключается в разделении модульных тестов на их собственный test
каталог:
new_project/
antigravity/
antigravity.py
test/
test_antigravity.py
setup.py
etc.
Мой вопрос просто в том, как обычно выполняются тесты? Я подозреваю, что это очевидно для всех, кроме меня, но вы не можете просто запустить python test_antigravity.py
из тестового каталога, поскольку его import antigravity
завершится ошибкой, поскольку модуля нет в path.
Я знаю, что мог бы изменить 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 discovery, который обнаружит и запустит все тесты для вас, они должны быть названы модулями или пакетами test*.py
(могут быть изменены с помощью -p, --pattern
флага):
$ cd new_project
$ python -m unittest discover
$ # Also works without discover for Python 3
$ # as suggested by @Burrito in the comments
$ python -m unittest
При этом будут запущены все test*.py
модули внутри test
пакета.
Здесь вы можете найти обновленную официальную документацию discovery
.
Ответ 2
У меня долгое время была одна и та же проблема. Недавно я выбрал следующую структуру каталогов:
project_path
├── Makefile
├── src
│ ├── script_1.py
│ ├── script_2.py
│ └── script_3.py
└── tests
├── __init__.py
├── test_script_1.py
├── test_script_2.py
└── test_script_3.py
и в __init__.py
скрипте тестовой папки я пишу следующее:
import os
import sys
PROJECT_PATH = os.getcwd()
SOURCE_PATH = os.path.join(
PROJECT_PATH,"src"
)
sys.path.append(SOURCE_PATH)
Для общего доступа к проекту очень важен Makefile, потому что он обеспечивает правильное выполнение скриптов. Вот команда, которую я ввел в Makefile:
run_tests:
python -m unittest discover .
Makefile важен не только из-за команды, которую он запускает, но и из-за того, откуда он запускается. Если бы вы запустили cd в tests и выполнили python -m unittest discover .
, это не сработало бы, потому что скрипт init в unit_tests вызывает os.getcwd() , который затем указал бы неверный абсолютный путь (который был бы добавлен к sys.path, и у вас не было бы исходной папки). Скрипты будут выполняться, поскольку discover найдет все тесты, но они не будут выполняться должным образом. Итак, Makefile существует, чтобы избежать необходимости помнить об этой проблеме.
Мне действительно нравится этот подход, потому что мне не нужно прикасаться к моей папке src, моим модульным тестам или переменным окружения, и все работает гладко.
Ответ 3
Самое простое решение для ваших пользователей - предоставить исполняемый скрипт (runtests.py
или что-то подобное), который загружает необходимую тестовую среду, включая, при необходимости, временное добавление вашего корневого каталога проекта в sys.path
. Это не требует от пользователей установки переменных окружения, что-то вроде этого прекрасно работает в bootstrap-скрипте:
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 и более ранних версий) теперь имеет встроенное обнаружение тестов, так что nose больше не нужен, если вы хотите автоматическое обнаружение тестов: ваши инструкции пользователя могут быть такими же простыми, как python -m unittest discover
.
Ответ 4
Обычно я создаю скрипт "run tests" в каталоге проекта (тот, который является общим как для исходного каталога, так и для 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 (из Как мне запускать все модульные тесты 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
использовать свои тестовые модули. Недостатком является то, что вам потребуется больше вспомогательного кода для выполнения конкретного теста... Я просто запускаю их все каждый раз.