Куда идут юнит-тесты Python?

490

Если вы пишете библиотеку или приложение, куда идут файлы модульного теста?

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

Есть ли здесь лучшая практика?

Readonly
источник
4
Есть много приемлемых ответов на этот вопрос, и это делает его не вопросом для сайта Q / A. Люди должны понимать, что не все большие полезные вопросы можно задать на SO. Создатели выбирают для этого, и мы должны следовать их правилам.
Wouter J
121
Людям также нужно понимать, что когда SO-страницы оказываются в числе самых популярных хитов для поиска в Google, то, возможно, лучше просто идти по течению, чем придерживаться официальной доктрины SO.
Кристофер Барбер
6
@ChristopherBarber люди также должны понимать, что решения и агрессивное закрытие - это то, что в итоге заработало ТАК ужасную репутацию сайта, я бы не стал задавать вопросы, если у меня действительно, действительно, нет лучшего выбора.
Стефано Борини

Ответы:

200

Для файла module.pyобычно test_module.pyвызывается модульный тест , следуя соглашениям Python по присвоению имен.

Есть несколько общепринятых мест для размещения test_module.py:

  1. В том же каталоге, что и module.py.
  2. В ../tests/test_module.py(на том же уровне, что и каталог кодов).
  3. В tests/test_module.py(один уровень под каталогом кода).

Я предпочитаю № 1 за простоту поиска тестов и их импорта. Какую бы систему сборки вы не использовали, ее можно легко настроить для запуска файлов, начиная с test_. На самом деле, шаблон по умолчанию unittest, используемый для обнаружения теста, являетсяtest*.py .

user6868
источник
12
Протокол load_tests ищет файлы с именем test * .py по умолчанию. Кроме того, этот лучший результат Google и документация по unittest используют формат test_module.py.
dfarrell07
6
Использование foo_test.py может сохранить комбинацию клавиш или два при использовании табуляции, так как у вас нет группы файлов, начинающихся с 'test_'.
можжевельник
11
@juniper, следуя вашим мыслям, module_test появится в автозаполнении, когда вы кодируете. Это может сбивать с толку или раздражать.
Медейрос
11
При развертывании кода мы не хотим развертывать тесты на наших производственных площадках. Таким образом, их легко хранить в одном каталоге ./tests/test_blah.py, когда мы выполняем развертывание. Кроме того, некоторые тесты принимают образцы файлов данных, и их наличие в каталоге тестов жизненно важно, чтобы мы не развернули тестовые данные.
Кевин Дж. Райс
1
@ KevinJ.Rice Разве вы не должны проверять, работает ли код на ваших производственных площадках?
эндолиты
67

Только 1 тестовый файл

Если имеется только 1 тестовый файл, рекомендуется поместить его в каталог верхнего уровня:

module/
    lib/
        __init__.py
        module.py
    test.py

Запустите тест в CLI

python test.py

Множество тестовых файлов

Если есть много тестовых файлов, поместите его в testsпапку:

module/
    lib/
        __init__.py
        module.py
    tests/
        test_module.py
        test_module_function.py
# test_module.py

import unittest
from lib import module

class TestModule(unittest.TestCase):
    def test_module(self):
        pass

if __name__ == '__main__':
    unittest.main()

Запустите тест в CLI

# In top-level /module/ folder
python -m tests.test_module
python -m tests.test_module_function

использование unittest discovery

unittest discovery найдете все тесты в папке пакета.

Создать __init__.pyв tests/папке

module/
    lib/
        __init__.py
        module.py
    tests/
        __init__.py
        test_module.py
        test_module_function.py

Запустите тест в CLI

# In top-level /module/ folder

# -s, --start-directory (default current directory)
# -p, --pattern (default test*.py)

python -m unittest discover

Ссылка

Модуль модульного тестирования

Steely Wing
источник
51

Обычной практикой является размещение каталога tests в том же родительском каталоге, что и ваш модуль / пакет. Так что, если ваш модуль называется foo.py, ваш макет каталога будет выглядеть так:

parent_dir/
  foo.py
  tests/

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

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

Cristian
источник
16
Я хотел бы сделать это, но я не могу заставить это работать. Для запуска тестов я нахожусь в parent_dir и набираю: «python tests \ foo_test.py» и в foo_test.py: «from ..foo импортировать это, то, другое». относительный импорт в непакетированном виде "И parent_dir, и тесты содержат в себе init .py, поэтому я не уверен, почему они не являются пакетами. Я подозреваю, что это потому, что скрипт верхнего уровня, который вы запускаете из командной строки, не может считаться (частью) пакета, даже если он находится в директории с init .py. Так как мне запустить тесты? Я работаю над Windows сегодня, благослови мои хлопковые носки.
Джонатан Хартли
4
Лучший способ - я обнаружил - запустить модульные тесты - это установить библиотеку / программу, а затем запустить модульные тесты с носом. Я бы порекомендовал virtualenv и virtualenvwrapper, чтобы сделать это намного проще.
Кристиан
@Tartley - вам нужен файл init .py в вашей директории 'tests', чтобы сработал импорт Absolution . У меня этот метод работает с носом, поэтому я не уверен, почему у вас проблемы.
cmcginty
4
Спасибо Кейси, но у меня есть файл init .py во всех соответствующих каталогах. Я не знаю, что я делаю неправильно, но у меня есть эта проблема во всех моих проектах Python, и я не могу понять, почему никто другой не делает. О дорогой.
Джонатан Хартли
8
Одно из решений для моей проблемы, с Python2.6 или новее, - запустить тесты из корня вашего проекта, используя: python -m project.package.tests.module_tests (вместо python project / package / tests / module_tests.py) , Это помещает каталог тестового модуля в путь, поэтому тесты могут затем выполнить относительный импорт в свой родительский каталог, чтобы получить тестируемый модуль.
Джонатан Хартли
32

У нас был тот же вопрос при написании Pythoscope ( https://pypi.org/project/pythoscope/ ), который генерирует модульные тесты для программ на Python. Мы опрашивали людей на тестировании в списке Python, прежде чем мы выбрали каталог, было много разных мнений. В итоге мы решили поместить каталог «tests» в тот же каталог, что и исходный код. В этом каталоге мы генерируем тестовый файл для каждого модуля в родительском каталоге.

Пол Хильдебрандт
источник
2
Потрясающие! Просто запустил Pythoscope (отличный логотип) на каком-то устаревшем коде, и это было потрясающе. Мгновенные передовые практики, и вы можете заставить других разработчиков заполнить теперь не пройденные тестовые заглушки. Теперь, чтобы соединить это в бамбук? :)
Будет
27

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

if __name__ == '__main__':
   do tests...

блок. Это завершает добавление документации к файлу в качестве «примера кода» о том, как использовать проверяемый файл python.

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

if __name__ == '__main__':
   import tests.thisModule
   tests.thisModule.runtests

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

Томас Эндрюс
источник
"как Джереми Кантрелл выше отмечает" где?
эндолит
Мне нравится этот способ работы. Самый простой из редакторов может быть настроен для запуска вашего файла с помощью горячей клавиши, поэтому при просмотре кода вы можете запускать тесты в одно мгновение. К сожалению, на языках, отличных от Python, это может выглядеть ужасно, поэтому, если вы находитесь в магазине C ++ или Java, ваши коллеги могут не одобрить это. Это также не очень хорошо работает с инструментами покрытия кода.
Кили
19

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

Основная причина для этого: рефакторинг .

Когда я перемещаю вещи, я хочу, чтобы тестовые модули перемещались вместе с кодом; легко потерять тесты, если они находятся в отдельном дереве. Давайте будем честными, рано или поздно вы получите совершенно другую структуру папок, такую ​​как django , flask и многие другие. Что хорошо, если тебе все равно.

Главный вопрос, который вы должны задать себе:

Я пишу:

  • а) многоразовая библиотека или
  • б) создание проекта, чем связывает воедино несколько разделенных модулей?

Если:

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

Но также легко исключить распространение тестов, когда они смешаны с основными папками; поместите это в setup.py :

find_packages("src", exclude=["*.tests", "*.tests.*", "tests.*", "tests"]) 

Если б:

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

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

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

Януш Сконечны
источник
1
В чем проблема с развертыванием тестов на производстве? по моему опыту, это очень полезно: позволяет пользователям фактически запускать тесты в своей среде ... когда вы получаете отчеты об ошибках, вы можете попросить пользователя запустить тестовый набор, чтобы убедиться, что все в порядке, и отправить горячие исправления для теста люкс прямо ... к тому же , это не потому , что вы положили свои тесты в module.tests , что она будет в конечном итоге в производстве, если вы не сделали что - то неправильно в вашем файле настройки ...
anarcat
2
Как я уже писал в своем ответе, я обычно не разделяю тесты. Но. Помещение тестов в дистрибутивный пакет может привести к выполнению вещей, которые вы обычно не хотели бы в производственной среде (например, некоторый код уровня модуля). Конечно, это зависит от того, как написаны тесты, но чтобы быть в безопасности, если вы их оставите, никто по ошибке не выполнит что-то вредное. Я не против включения тестов в дистрибутивы, но я понимаю, что, как правило, это безопаснее. А если поместить их в отдельную папку, их будет очень легко не включать в dist.
Януш Сконечны
15

Я использую tests/каталог, а затем импортирую основные модули приложения, используя относительный импорт. Так что в MyApp / tests / foo.py могут быть:

from .. import foo

импортировать MyApp.fooмодуль.

Джон Милликин
источник
5
«ValueError: Попытка относительного импорта в
неупакованном виде
12

Я не верю, что существует установленная «лучшая практика».

Я поместил свои тесты в другой каталог за пределами кода приложения. Затем я добавляю основной каталог приложения в sys.path (позволяя вам импортировать модули из любого места) в моем скрипте бегуна тестов (который также выполняет некоторые другие вещи) перед запуском всех тестов. Таким образом, мне никогда не придется удалять каталог tests из основного кода, когда я его выпускаю, что экономит мое время и усилия, если даже очень мало.

dwestbrook
источник
3
Затем я добавляю основной каталог приложения в sys.path . Проблема в том, что ваши тесты содержат несколько вспомогательных модулей (например, тестовый веб-сервер), и вам необходимо импортировать такие вспомогательные модули из ваших собственных тестов.
Петр Доброгост
Вот как это выглядит для меня:os.sys.path.append(os.dirname('..'))
Мэтт М.
11

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

                              <Main Package>
                               /          \
                              /            \
                            lib           tests
                            /                \
             [module1.py, module2.py,  [ut_module1.py, ut_module2.py,
              module3.py  module4.py,   ut_module3.py, ut_module.py]
              __init__.py]

Таким образом, когда вы упаковываете эти библиотеки, используя rpm, вы можете просто упаковать модули основной библиотеки (только). Это помогает ремонтопригодности, особенно в гибкой среде.

Рахул Бисвас
источник
1
Я понял, что одним из потенциальных преимуществ этого подхода является то, что тесты могут разрабатываться (и, возможно, даже контролироваться по версии) как самостоятельное приложение. Конечно, у каждого преимущества есть недостаток - поддержание симметрии в основном делает «учет двойной записи» и делает рефакторинг более трудоемким.
Яша
Последующий мягкий вопрос: почему гибкая среда особенно подходит для этого подхода? (т.е. как насчет гибкого рабочего процесса, который делает симметричную структуру каталогов столь полезной?)
Jasha
11

Я рекомендую вам проверить некоторые основные проекты Python на GitHub и получить некоторые идеи.

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

Например, если у вас есть структура каталогов, например:

myPackage/
    myapp/
       moduleA/
          __init__.py
          module_A.py
       moduleB/
          __init__.py
          module_B.py
setup.py

После добавления тестовой папки у вас будет такая структура каталогов, как:

myPackage/
    myapp/
       moduleA/
          __init__.py
          module_A.py
       moduleB/
          __init__.py
          module_B.py
test/
   unit/
      myapp/
         moduleA/
            module_A_test.py
         moduleB/
            module_B_test.py
   integration/
          myapp/
             moduleA/
                module_A_test.py
             moduleB/
                module_B_test.py
setup.py

Многие правильно написанные пакеты Python используют одну и ту же структуру. Очень хороший пример - пакет Boto. Проверьте https://github.com/boto/boto

Сыпь
источник
1
Рекомендуется использовать «тесты» вместо «тест», потому что «тест» - это встроенный модуль Python. docs.python.org/2/library/test.html
brodul
Не всегда .. например matplotlibимеет его в matplotlib/lib/matplotlib/tests( github.com/matplotlib/matplotlib/tree/… ), sklearnимеет его в scikitelearn/sklearn/tests( github.com/scikit-learn/scikit-learn/tree/master/sklearn/tests )
alpha_989
7

Как я это делаю ...

Структура папки:

project/
    src/
        code.py
    tests/
    setup.py

Setup.py указывает на src / как местоположение, содержащее мои модули проектов, затем я запускаю:

setup.py develop

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

setup.py tests

Используя тот тестовый бегун, который я настроил.

Дейл Рейди
источник
Вы, кажется, переписали термин «модуль». Большинство программистов на Python, вероятно, подумают, что модуль - это файл, который вы назвали code.py. Было бы разумнее называть каталог верхнего уровня «проектом».
Blokeley
4

Я предпочитаю выполнять тестовый каталог. Это означает, что импорт становится немного сложнее. Для этого у меня есть два решения:

  1. Используйте setuptools. Затем вы можете перейти test_suite='tests.runalltests.suite'в setup(), и может запускать тесты просто:python setup.py test
  2. Установите PYTHONPATH при запуске тестов: PYTHONPATH=. python tests/runalltests.py

Вот как эта штука поддерживается кодом в M2Crypto:

Если вы предпочитаете проводить тесты с тестами носа, вам может потребоваться сделать что-то немного другое.

bstpierre
источник
3

Мы используем

app/src/code.py
app/testing/code_test.py 
app/docs/..

В каждом тестовом файле мы вставляем ../src/в sys.path. Это не самое лучшее решение, но оно работает. Я думаю, было бы замечательно, если бы кто-то придумал что-то вроде maven в java, которое дает вам стандартные соглашения, которые просто работают, независимо от того, над каким проектом вы работаете.

Андре
источник
1

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

>>> import module
>>> module.method('test')
'testresult'

Для других более сложных тестов я бы поставил их либо в, ../tests/test_module.pyлибо в tests/test_module.py.


источник
1

В C # я обычно разделял тесты на отдельную сборку.

В Python - пока что - я обычно пишу тесты документов, где тест находится в строке документации функции, или помещаю их в if __name__ == "__main__"блок в нижней части модуля.

Джордж В. Рейли
источник
0

При написании пакета с именем "foo" я помещу модульные тесты в отдельный пакет "foo_test". Модули и подпакеты будут иметь то же имя, что и модуль пакета SUT. Например, тесты для модуля foo.xy находятся в файле foo_test.xy. Файлы __init__.py каждого пакета тестирования содержат набор AllTests, который включает все наборы тестов пакета. setuptools предоставляет удобный способ указать основной пакет тестирования, так что после «python setup.py development» вы можете просто использовать «python setup.py test» или «python setup.py test -s foo_test.x.SomeTestSuite» для просто конкретный набор.

Себастьян Риттау
источник
0

Я помещаю свои тесты в ту же директорию, что и тестируемый код (CUT); для foo.pyтестов будет в foo_ut.pyили подобное. (Я настраиваю тестовый процесс обнаружения, чтобы найти их.)

Это помещает тесты рядом с кодом в список каталогов, делая очевидным наличие тестов, и делает открытие тестов настолько простым, насколько это возможно, когда они находятся в отдельном файле. (Для редакторов командной строки vim foo*и при использовании графического браузера файловой системы просто щелкните файл CUT, а затем непосредственно смежный файл теста.)

Как уже отмечали другие , это также облегчает рефакторинг и извлечение кода для использования в других местах, если это когда-либо понадобится.

Мне действительно не нравится идея помещать тесты в совершенно другое дерево каталогов; зачем усложнять разработчикам открытие тестов при открытии файла с помощью CUT? Не то, чтобы подавляющее большинство разработчиков так увлеклись написанием или настройкой тестов, что игнорировали бы любой барьер для этого, вместо того, чтобы использовать этот барьер в качестве оправдания. (Напротив, по моему опыту; даже когда вы делаете это как можно проще, я знаю многих разработчиков, которые не могут быть обеспокоены написанием тестов.)

CJS
источник
-2

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

Так что я:

приложение/
 appfile.py
тестовое задание/
 appfileTest.py

Мне нужно будет посмотреть, как это пойдет по мере продвижения к более крупным проектам.

quamrana
источник
4
Привет, camelCase немного странно в мире питонов.
Стюарт Аксон