Запуск unittest з типовою структурою тестового каталогу


701

Дуже поширена структура каталогів навіть для простого модуля Python, схоже, полягає в тому, щоб розділити тести одиниць на власну testдиректорію:

new_project/
    antigravity/
        antigravity.py
    test/
        test_antigravity.py
    setup.py
    etc.

наприклад, див. як цей проект Python .

Моє запитання просто Який звичайний спосіб насправді виконувати тести? Я підозрюю, що це очевидно для всіх, окрім мене, але ви не можете просто запустити python test_antigravity.pyз тестового каталогу, оскільки його import antigravityвийде з ладу, оскільки модуль не на шляху.

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

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

Отже, якби ви тільки що завантажили джерело в мій новий проект, як би ви запустили одиничні тести? Я вважаю за краще відповідь, яка дозволить мені сказати своїм користувачам: "Для запуску одиничних тестів зробіть X".


5
@EMP Правильне рішення, коли вам потрібно встановити шлях пошуку, - це ... встановити шлях пошуку. Яке рішення ви очікували?
Карл Мейєр

7
@CarlMeyer Ще одним кращим рішенням є використання unittestінтерфейсу командного рядка, як описано в моїй відповіді нижче, щоб вам не довелося додавати каталог у шлях.
П’єр

13
Те ж саме. Я просто почав писати свої найперші тестові одиниці для крихітного проекту Python і кілька днів намагався обґрунтувати те, що я не можу легко виконати тест, зберігаючи свої джерела в каталозі src та тести в тестовому каталозі, мабуть, з будь-якою з існуючих рамок тестів. Я врешті прийму речі, придумаю спосіб; але це було дуже неприємне вступ. (І я ветеран підрозділу тестування за межами Python.)
Атес Горал

Відповіді:


657

Найкращим рішенням, на мою думку, є використання 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
$ # Also works without discover for Python 3
$ # as suggested by @Burrito in the comments
$ python -m unittest

Це запустить усі test*.pyмодулі всередині testпакета.


53
python -m unittest discoverзнайде та запустить тести в testкаталозі, якщо вони будуть названі test*.py. Якщо ви назвали підкаталог tests, використовуйте python -m unittest discover -s tests, а якщо ви назвали тестові файли antigravity_test.py, використовуйте python -m unittest discover -s tests -p '*test.py' Імена файлів можуть використовувати підкреслення, але не тире.
Mike3d0g

10
Мені це не вдається на Python 3 з помилкою ImportError: No module named 'test.test_antigravity'через конфлікт із тестовим підмодулем бібліотеки unittest. Можливо, експерт може підтвердити та змінити назву підкаталогу відповідей, наприклад, "тести" (множина).
expz

10
Мої test_antigravity.pyвсі ще видає помилку імпорту для обох import antigravityі from antigravity import antigravity, як добре. У мене є обидва __init_.pyфайли, і я дзвоню python3 -m unittest discoverз new projectкаталогу. Що ще може бути не так?
імрек

20
Файл test/__init__.pyтут є вирішальним, навіть якщо він порожній
Франсуа

3
@ Mike3d0g не впевнений, чи ти мав на увазі, що ім'я каталогу testспеціальне ... але тільки для запису це не так. : P так само добре python -m unittest discoverпрацює з тестовими файлами . tests/test/
ryan

49

Найпростішим рішенням для ваших користувачів є надання виконавчого сценарію ( 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.


Я поклав кілька тестів у підпапку на кшталт "Майор майора". Вони можуть працювати з python -m unittest виявити, але як я можу вибрати для запуску лише одного з них. Якщо я запускаю python -m unittest тести / testxxxxx, це не вдається для видачі шляху. Оскільки в режимі dicovery вирішується все, я би сподівався, що є ще одна хитрість вирішити проблему шляху без виправлення ручного кодування, яку ви запропонуєте в першій точці
Фредерік Базін

2
@FredericBazin Не використовуйте відкриття, якщо ви хочете лише один тестовий або тестовий файл, просто назвіть модуль, який потрібно запустити. Якщо ви назвали його як модульний пунктир (а не шлях до файлу), він може правильно визначити шлях пошуку. Детальну інформацію див. У відповіді Петра.
Карл Мейєр

Цей злом був корисним у сценарії, коли мені довелося запустити щось на кшталт python -m pdb tests\test_antigravity.py. Всередині pdb я виконав, sys.path.insert(0, "antigravity")що дозволило оператору імпорту вирішувати так, ніби я запускаю модуль.
ixe013

23

Я, як правило, створюю сценарій "запуску тестів" у каталозі проектів (той, який є загальним для вихідного каталогу та 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у своїх тестових модулях. Мінус у тому, що вам знадобиться більше коду підтримки для виконання певного тесту ... Я просто запускаю їх кожен раз.


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

18

Із статті, до якої ви посилаєтесь:

Створіть файл test_modulename.py і поставте в нього свої тестові одиничні тести. Оскільки модулі тестування знаходяться в окремому каталозі від вашого коду, можливо, вам потрібно буде додати батьківський каталог вашого модуля до свого PYTHONPATH, щоб запустити їх:

$ cd /path/to/googlemaps

$ export PYTHONPATH=$PYTHONPATH:/path/to/googlemaps/googlemaps

$ python test/test_googlemaps.py

Нарешті, є ще одна популярна рамка тестування модулів для Python (це важливо!), Ніс. ніс допомагає спростити і розширити вбудований блок testtest (він, наприклад, може автоматично знайти тестовий код і налаштувати ваш PYTHONPATH для вас), але він не входить до стандартного розподілу Python.

Можливо, вам слід дивитись на ніс, як це підказує?


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

4
То як виглядає ваш пітон шлях після того, як ви працювали над сотнею проектів? Чи повинен я вручну зайти і прибрати свій шлях? Якщо так, це одіозна конструкція!
jeremyjjbrown

11

У мене була така ж проблема, з окремою папкою тестів для одиниць. Із зазначених пропозицій я додаю абсолютний шлях до джерелаsys.path .

Перевага наступного рішення полягає в тому, що можна запустити файл, test/test_yourmodule.pyне спершу змінюючи його в тестовому каталозі:

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

9

якщо ви запускаєте "python setup.py razvija", пакет буде на шляху. Але ви, можливо, не хочете цього робити, тому що ви можете заразити вашу систему python, тому такі інструменти, як virtualenv та buildout, існують.


7

Рішення / Приклад модуля Python unittest

Враховуючи таку структуру проекту:

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)

5

Використовуйте setup.py developдля того, щоб ваш робочий каталог став частиною встановленого середовища Python, а потім запустіть тести.


Це отримує мене, invalid command 'develop'і ця опція не згадується, якщо я прошу setup.py --help-commands. Чи потрібно мати щось setup.pyсаме по собі, щоб це працювало?
майор майора

Це нормально - проблема в тому, що я не вистачав import setuptoolsзі свого setup.pyфайлу. Але я думаю, що це дійсно доводить, що це не буде працювати весь час для чужих модулів.
майор-майор

1
Якщо у вас є pip , ви можете використовувати його для встановлення вашого пакета в режимі "редагований" . pip install -e .Це також додає пакунок до середовища Python, не копіюючи джерело, дозволяючи продовжувати редагувати його там, де він лежить.
Ерік Сміт

pip install -e .це точно те ж саме python setup.py develop, що він просто маніпулює вами setup.pyвикористовувати setuptools, навіть якщо він насправді не працює, тому він працює в будь-якому випадку.
Карл Мейєр

5

Якщо ви використовуєте код VS і ваші тести розташовані на тому ж рівні, що і ваш проект, то запущений і налагоджений код не виходить з поля. Що ви можете зробити, це змінити файл start.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: \ ВАШ \ ПІТОН \ ПРОЕКТ \ ROOT_DIRECTORY

шлях до вашого проекту, і ви зможете використовувати тести блоку налагодження з коду VS


5

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

python -m unittest discover -s ../test

Якщо ви хочете помістити це у пакетний файл у каталозі свого проекту, ви можете зробити це:

setlocal & cd src & python -m unittest discover -s ../test

5

У мене була дана проблема. Нещодавно я вибрав таку структуру каталогів:

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 в тестах і робите python -m unittest discover ., це не буде працювати, оскільки скрипт init в unit_tests викликає os.getcwd (), який би вказував на неправильний абсолютний шлях (який би додався до sys.path, і ви б пропустили папку-джерело). Сценарії працюватимуть з моменту виявлення всіх тестів, але вони не працюватимуть належним чином. Тож Makefile є там, щоб не запам'ятати цю проблему.

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

Дайте мені знати, якщо вам це подобається.

Сподіваюся, що це допомагає,


4

Далі йде моя структура проекту:

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()

4

Який звичайний спосіб насправді виконувати тести

Я використовую 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()

4

Можна використовувати обгортку, яка виконує вибрані або всі тести.

Наприклад:

./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 в каталозі?


4

Python 3+

Додавання до @Pierre

Використовуючи 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навіть якщо порожнє в іншому випадку не вийде.


2

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

Спочатку майте тест файлу / 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

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


2

Якщо у вашому тестовому каталозі є кілька каталогів, вам доведеться додати до кожного каталогу __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)

1

Цей сценарій 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файлі, який би обтяжував ваш пакунок / накладні витрати на пам'ять під час виробництва.


1

Таким чином ви зможете запускати тестові сценарії звідки завгодно, не возившись із системними змінними з командного рядка.

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

import sys, os

sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.realpath(__file__))))

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

Ви, очевидно, можете змінити файл project_path_hack, щоб він відповідав основній локації папки проекту.


0

Якщо ви шукаєте рішення лише для командного рядка:

На основі такої структури каталогів (узагальненої з виділеним каталогом джерел):

new_project/
    src/
        antigravity.py
    test/
        test_antigravity.py

Windows : (в new_project)

$ set PYTHONPATH=%PYTHONPATH%;%cd%\src
$ python -m unittest discover -s test

Дивіться це питання, якщо ви хочете використовувати це в пакеті for-loop.

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, якщо необхідно.


0

Вам справді слід скористатися інструментом "pip".

Використовуйте pip install -e .для встановлення вашого пакета в режимі розробки. Це дуже хороша практика, рекомендована pytest (дивіться їх документацію щодо передового досвіду , де ви також можете знайти два макети проекту, які слід дотримуватися).


Чому спростовують цю відповідь? Я прочитав прийняту відповідь, і хоча це було непогано, pytestчи краще запустити тести через кольоровий вихід консолі з інформацією про сліди стека та детальною інформацією про помилку твердження.
aliopi
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.