it-roy-ru.com

Выполнение юнит-теста с типичной структурой тестового каталога

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

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

например, посмотрите это проект Python, как .

У меня простой вопрос: Какой обычный способ на самом деле запустить тесты? Я подозреваю, что это очевидно для всех, кроме меня, но вы не можете просто запустить python test_antigravity.py из тестового каталога, так как его import antigravity потерпит неудачу, так как модуль не на пути.

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

Другая альтернатива - просто скопировать тестовый файл в другой каталог, но он выглядит немного глупым и упускает смысл начинать их с отдельного каталога.

Итак, если бы вы только что загрузили исходный код в мой новый проект, как бы вы запустили модульные тесты? Я бы предпочел ответ, который позволил бы мне сказать моим пользователям: «Чтобы запустить модульные тесты, сделайте X».

553
Major Major

На мой взгляд, наилучшим решением является использование 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.

520
Pierre

Самым простым решением для ваших пользователей является предоставление исполняемого скрипта (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 и более ранних версий) теперь имеет встроенную функцию test discovery , поэтому в переноске больше нет необходимости, если вы хотите автоматическое обнаружение тестов: ваши пользовательские инструкции могут быть такими же простыми, как "python -m unittest explore".

45
Carl Meyer

Обычно я создаю скрипт «run tests» в каталоге проекта (тот, который является общим как для исходного каталога, так и для test), который загружает мой «All Tests» комплект. Обычно это стандартный код, поэтому я могу использовать его от проекта к проекту.

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 в ваших тестовых модулях. Недостатком является то, что вам потребуется больше кода поддержки для выполнения определенного теста ... Я просто запускаю их каждый раз.

19
stw_dev

Из статьи, на которую вы ссылаетесь:

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

$ cd /path/to/googlemaps

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

$ python test/test_googlemaps.py

Наконец, есть еще один популярный Фреймворк модульного тестирования для Python (это так важно!), нос. нос помогает упростить и расширить встроенную функцию Фреймворк unittest (например, может автоматически найти ваш тестовый код и настроить PYTHONPATH для вас), но он не включен в стандартный дистрибутив Python.

Возможно, вы должны смотреть на нос , как это предполагает?

18
Mark Byers

если вы запустите "python setup.py development", то пакет будет в пути. Но вы можете не захотеть этого делать, потому что вы можете заразить вашу системную установку python, поэтому существуют такие инструменты, как virtualenv и buildout

8
Tom Willis

У меня была такая же проблема, с отдельной папкой юнит-тестов. Из упомянутых предложений я добавляю абсолютный исходный путь к 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
8
andpei

Если вы используете 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:\ВАШЕГО\ПИТОНА\PROJECT\ROOT_DIRECTORY

путь к вашему проекту, и вы сможете использовать отладочные юнит-тесты из VS Code

5
Vlad Bezden

Решение/Пример для модуля юнит-теста 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)
5
Derek Soike

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

5
Ned Batchelder

Можно использовать упаковщик, который запускает выбранные или все тесты.

Например:

./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
kenorb

Я заметил, что если вы запускаете интерфейс командной строки unittest из своего каталога "src", то импорт работает корректно без изменений.

python -m unittest discover -s ../test

Если вы хотите поместить это в командный файл в каталоге вашего проекта, вы можете сделать это:

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

Ниже приводится структура моего проекта:

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 __== "__main__":
    unittest.main()
3
rolika

Каков обычный способ выполнения тестов?

Я использую Python 3.6.2

cd new_project

pytest test/test_antigravity.py

Чтобы установить pytest: Sudo pip install pytest

Я не установил никакой переменной пути, и мой импорт не завершается с той же «тестовой» структурой проекта.

Я закомментировал этот материал: if __== '__main__' вот так:

test_antigravity.py

import antigravity

class TestAntigravity(unittest.TestCase):

    def test_something(self):

        # ... test stuff here


# if __== '__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()
3
aliopi

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
eusoubrasileiro

Вы не можете импортировать из родительского каталога без некоторого вуду. Вот еще один способ, который работает по крайней мере с 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

Обратите внимание на то, что причина этого предложения о том, что 

  • import test.context завершается ошибкой при запуске с "python test_antigravity.py" и 
  • import context завершается ошибкой при запуске с "python -m unittest" из каталога new_project.

С этим обманом они оба работают. 

Теперь вы можете запустить все тестовые файлы в тестовой директории с помощью:

$ pwd
/projects/new_project
$ python -m unittest

или запустите отдельный тестовый файл с:

$ cd test
$ python test_antigravity

Хорошо, это не намного красивее, чем содержание context.py в test_antigravity.py, но, возможно, немного. Предложения приветствуются.

2
tjk

Этот 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
Egbert S

Если у вас есть несколько каталогов в вашем тестовом каталоге, то вы должны добавить в каждый каталог файл __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)
0
Qlimax

Если вы ищете решение только для командной строки:

На основе следующей структуры каталогов (обобщенной с выделенным каталогом источника):

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

Windows: (в new_project)

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

Смотрите этот вопрос , если вы хотите использовать это в цикле for.

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
pj.dewitte