Выполнение 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, чтобы он соответствовал местоположению вашей основной папки проекта.