Куди йдуть випробування блоку Python?


490

Якщо ви пишете бібліотеку чи додаток, куди йдуть файли тестових програм?

Приємно відокремлювати тестові файли від основного коду програми, але незручно їх розміщувати у підкаталозі "тести" всередині кореневого каталогу додатків, оскільки це ускладнює імпорт модулів, які ви будете тестувати.

Чи є тут найкраща практика?


4
Є багато прийнятних відповідей на це питання, і це не робить питання для Q / A сайту. Люди повинні розуміти, що не всі великі корисні запитання можуть бути задані на ТАК. Форматори вибирають саме це, і ми повинні слідувати їх правилам.
Wouter J

121
Люди також повинні розуміти, що коли сторінки SO в кінцевому підсумку є найкращим хітом для пошуку в Google, можливо, краще просто йти з потоком, а не дотримуватися офіційної доктрини SO.
Крістофер Барбер

6
@ChristopherBarber також повинні розуміти, що рішення та агресивне закриття - це те, що в кінцевому підсумку заробляє ТАК жахливу репутацію сайту, я б швидше не ходив задавати питання, якщо я справді не маю кращого варіанту.
Стефано Борині

Відповіді:


200

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

Є кілька загальноприйнятих місць, які можна поставити test_module.py:

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

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


12
Протокол load_tests шукає файли з назвою test * .py за замовчуванням. Крім того, і цей головний результат Google, і ця документація на одиничні тести використовують формат 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

Довідково

Блок тестування блоку


51

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

parent_dir/
  foo.py
  tests/

Звичайно, немає жодного способу зробити це. Ви також можете зробити тест-підкаталог та імпортувати модуль, використовуючи абсолютний імпорт .

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


16
Я хотів би це зробити, але я не можу змусити його працювати. Щоб запустити тести, я перебуваю в parent_dir і введіть: "python testing \ foo_test.py", а в foo_test.py: "from ..foo імпортуйте це, те, інше", що не вдається: "ValueError: спроба відносний імпорт у непакеті "І parent_dir, і тести мають init .py у них, тому я не впевнений, чому вони не є пакетами. Я підозрюю, що це тому, що сценарій верхнього рівня, який ви запускаєте з командного рядка, не можна вважати (частиною) пакетом, навіть якщо він знаходиться в режимі dir з init .py. Тож як я запускаю тести? Я сьогодні працюю над Windows, благословляйте свої бавовняні шкарпетки.
Джонатан Хартлі

4
Найкращий спосіб - я знайшов - запустити одиничні тести - встановити свою бібліотеку / програму, а потім запустити одиничні тести носом. Я б рекомендував virtualenv та virtualenvwrapper, щоб зробити це набагато простіше.
Крістіан

@Tartley - вам потрібен файл init .py у вашому каталозі "тести", щоб імпортувати скасування на роботу. У мене цей метод працює з носом, тому я не впевнений, чому у вас виникають проблеми.
cmcginty

4
Дякую Кейсі - але у мене є файл init .py у всіх відповідних каталогах. Я не знаю, що я роблю не так, але у мене є проблема у всіх моїх проектах Python, і я не можу зрозуміти, чому ніхто інший цього не робить. О боже.
Джонатан Хартлі

8
Одне рішення для моєї проблеми, з Python2.6 або новішою версією, ми запускаємо тести з кореня вашого проекту, використовуючи: python -m project.package.tests.module_tests (замість проекту / пакета / тестів / module_tests.py) . Це ставить каталог тестового модуля на шлях, тому тести можуть потім відносно імпортувати їх у батьківський каталог, щоб отримати модуль під тестом.
Джонатан Хартлі

32

У нас виникло те саме питання при написанні Pythoscope ( https://pypi.org/project/pythoscope/ ), який генерує одиничні тести для програм Python. Ми опитували людей на тестуванні в списку python, перш ніж обрати каталог, було багато різних думок. Врешті-решт ми вирішили помістити каталог тестів у ту саму директорію, що і вихідний код. У цьому каталозі ми генеруємо тестовий файл для кожного модуля в батьківському каталозі.


2
Дивовижно! Щойно побіг Пітоскоп (чудовий логотип), на якомусь спадковому коді, і це було дивовижно. Миттєві кращі практики, і ви можете змусити інших розробників заповнити невдалі тестові заглушки. Тепер підключити його до бамбука? :)
Буде

27

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

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

блок. Це закінчується додаванням документації до файлу як "приклад коду" для використання файлу python, який ви тестуєте.

Додам, я схильний писати дуже щільні модулі / класи. Якщо ваші модулі вимагають дуже великої кількості тестів, ви можете помістити їх в інший, але навіть тоді я все одно додаю:

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

Це дозволяє кожному, хто читає ваш вихідний код, знати, де шукати тестовий код.


"як Джеремі Кантрелл вище", де?
ендоліт

Мені подобається такий спосіб роботи. Найпростіші редактори можуть бути налаштовані на запуск файлу гарячою клавішею, тому, переглядаючи код, ви можете запустити тести за мить. На жаль, для інших мов, ніж Python, це може виглядати жахливо, тому якщо ви перебуваєте в магазині C ++ або Java, ваші колеги можуть на це нахмуритися. Він також не працює з інструментами покриття коду.
Кілі

19

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

Основна причина цього: рефакторинг .

Коли я пересуваю речі, я хочу, щоб тестові модулі рухалися разом із кодом; втратити тести легко, якщо вони знаходяться в окремому дереві. Будемо чесними, рано чи пізно ви закінчите зовсім іншу структуру папок, наприклад, джанго , фляшку та багато інших. Що добре, якщо вам все одно.

Головне питання, яке ви повинні задати собі, це:

Я пишу:

  • а) бібліотеку для багаторазового використання або
  • б) побудова проекту, ніж зв'язує разом деякі напіввідділені модулі?

Якщо:

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

Але також так само просто виключити тести з розповсюдження, коли вони змішуються з основними папками; помістіть це в setup.py :

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

Якщо b:

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

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

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


1
яка проблема насправді з розгортанням тестів на виробництво? на мій досвід, це дуже корисно: дозволяє користувачам реально проводити тести на їх оточенні ... коли ви отримуєте звіти про помилки, ви можете попросити користувача запустити тестовий набір, щоб переконатися, що там все добре, і надіслати гарячі патчі для тесту люкс прямо ... до того ж , це не тому , що ви поклали свої тести в module.tests , що вона буде в кінцевому підсумку у виробництві, якщо ви не зробили що - то неправильно в вашому файлі налаштування ...
anarcat

2
Як я писав у своїй відповіді, я зазвичай не розділяю тести. Але. Якщо помістити тести в дистрибутивний пакет, це може призвести до виконання матеріалів, які зазвичай не хотіли б у виробництві env (наприклад, код на рівні модуля). Звичайно, це залежить від того, як виписуються тести, але якщо ви будете захищені від них, ви не зможете запустити щось шкідливе помилково. Я не проти включення тестів у дистрибуції, але розумію, що як правило, це безпечніше. А розміщення їх в окремій папці робить дуже легко не включати їх у дистрибуцію.
Януш Сконієчний

15

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

from .. import foo

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


5
"ValueError: Спроба відносного імпорту в
непакеті

12

Я не вірю, що існує «найкраща практика».

Я ставлю свої тести в інший каталог поза кодом програми. Потім я додаю основний каталог додатків до sys.path (що дозволяє імпортувати модулі з будь-якого місця) у мій сценарій тестування бігуна (що також робить деякі інші речі) перед запуском усіх тестів. Таким чином, я ніколи не повинен видаляти каталог тестів з основного коду, коли випускаю його, економлячи час і зусилля, якщо ця невелика кількість.


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. Було б більше сенсу називати каталог верхнього рівня "проектом".
Блоклі

4

Я віддаю перевагу каталогу тестів на рівні рівнів. Це означає, що імпорт стає дещо складнішим. Для цього у мене є два рішення:

  1. Використовуйте setuptools. Тоді ви можете перейти test_suite='tests.runalltests.suite'до setup()тесту та запустити тести просто:python setup.py test
  2. Встановіть PYTHONPATH під час запуску тестів: PYTHONPATH=. python tests/runalltests.py

Ось як цей матеріал підтримується кодом у M2Crypto:

Якщо ви віддаєте перевагу запускати тести з носами, можливо, вам доведеться зробити дещо інше.


3

Ми використовуємо

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

У кожному з тестового файлу ми вставляємо ../src/в sys.path. Це не найприємніше рішення, але працює. Я думаю, що було б чудово, якби хтось придумав w / щось на кшталт maven in java, що дає стандартні умови, які просто працюють, незалежно від того, над яким проектом ви працюєте.


1

Якщо тести прості, просто покладіть їх у docstring - більшість тестових рамок для Python зможуть використовувати це:

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

Для інших більш задіяних тестів я б розміщував їх ../tests/test_module.pyабо в tests/test_module.py.


1

У C # я загалом розділив тести на окрему збірку.

У Python - поки що - я схильний або писати doctests, де тест знаходиться в docstring функції, або розміщувати їх у 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? Це не так, як переважна більшість розробників настільки захоплюються написанням або налаштуванням тестів, що вони ігнорують будь-який бар'єр для цього, замість того, щоб використовувати бар'єр як привід. (На моєму досвіді, навпаки; навіть коли ви це зробите максимально просто, я знаю багатьох розробників, які не можуть заважати писати тести.)


-2

Нещодавно я почав програмувати в Python, тому ще не мав шансів дізнатися найкращу практику. Але я написав модуль, який йде і знаходить усі тести і виконує їх.

Отже, у мене є:

додаток /
 appfile.py
тест /
 appfileTest.py

Мені доведеться побачити, як це відбувається в міру просування до великих проектів.


4
Привіт, camelCase - це трохи дивно у світі пітонів.
Стюарт Ексон
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.