Executando unittest com estrutura de diretório de teste típica


700

A estrutura de diretórios muito comum, mesmo para um módulo Python simples, parece separar os testes de unidade em seu próprio testdiretório:

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

por exemplo, veja este tutorial do projeto Python .

Minha pergunta é simplesmente: Qual é a maneira usual de realmente executar os testes? Eu suspeito que isso seja óbvio para todos, exceto eu, mas você não pode simplesmente executar a python test_antigravity.pypartir do diretório de teste, pois import antigravityisso falhará, pois o módulo não está no caminho.

Sei que poderia modificar o PYTHONPATH e outros truques relacionados ao caminho de pesquisa, mas não acredito que seja a maneira mais simples - tudo bem se você é o desenvolvedor, mas não é realista esperar que seus usuários usem se eles apenas quiserem verificar os testes. passagem.

A outra alternativa é apenas copiar o arquivo de teste para o outro diretório, mas parece um pouco idiota e perde o sentido de tê-los em um diretório separado para começar.

Então, se você tivesse baixado a fonte do meu novo projeto, como você executaria os testes de unidade? Prefiro uma resposta que me permita dizer aos meus usuários: "Para executar os testes de unidade, faça o X."


5
@EMP A solução adequada quando você precisa definir o caminho de pesquisa é ... definir o caminho de pesquisa. Que tipo de solução você esperava?
Carl Meyer

7
@CarlMeyer Outra solução melhor é usar a unittestinterface da linha de comandos, conforme descrito na minha resposta abaixo, para que você não precise adicionar o diretório ao caminho.
Pierre

13
O mesmo aqui. Eu apenas comecei a escrever meus primeiros testes de unidade em um pequeno projeto Python e levei vários dias tentando argumentar com o fato de que eu não posso executar facilmente um teste enquanto mantinha minhas fontes em um diretório src e testes em um diretório de teste, aparentemente com qualquer uma das estruturas de teste existentes. Acabarei aceitando as coisas, descobrindo uma maneira; mas esta foi uma introdução muito frustrante. (E eu sou uma unidade de teste veterano fora Python.)
Ates Goral

Respostas:


656

A melhor solução, na minha opinião, é usar a unittest interface da linha de comandos que adicionará o diretório ao diretório para sys.pathque você não precise (feito na TestLoaderclasse).

Por exemplo, para uma estrutura de diretórios como esta:

new_project
├── antigravity.py
└── test_antigravity.py

Você pode simplesmente executar:

$ cd new_project
$ python -m unittest test_antigravity

Para uma estrutura de diretórios como a sua:

new_project
├── antigravity
   ├── __init__.py         # make it a package
   └── antigravity.py
└── test
    ├── __init__.py         # also make test a package
    └── test_antigravity.py

E nos módulos de teste dentro do testpacote, você pode importar o antigravitypacote e seus módulos como de costume:

# 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

Executando um único módulo de teste:

Para executar um único módulo de teste, neste caso test_antigravity.py:

$ cd new_project
$ python -m unittest test.test_antigravity

Basta referenciar o módulo de teste da mesma maneira que você o importa.

Executando um único caso de teste ou método de teste:

Além disso, você pode executar um TestCasemétodo de teste único ou único:

$ python -m unittest test.test_antigravity.GravityTestCase
$ python -m unittest test.test_antigravity.GravityTestCase.test_method

Executando todos os testes:

Você também pode usar a descoberta de testes, que descobrirá e executará todos os testes para você; eles devem ser módulos ou pacotes nomeados test*.py(podem ser alterados com o -p, --patternsinalizador):

$ cd new_project
$ python -m unittest discover
$ # Also works without discover for Python 3
$ # as suggested by @Burrito in the comments
$ python -m unittest

Isso executará todos os test*.pymódulos dentro do testpacote.


53
python -m unittest discoverirá encontrar e executar testes no testdiretório se eles forem nomeados test*.py. Se você nomeou o subdiretório tests, use python -m unittest discover -s testse se você nomeou os arquivos de teste antigravity_test.py, use python -m unittest discover -s tests -p '*test.py' Nomes de arquivos podem usar sublinhados, mas não traços.
Mike3d0g

10
Isso falha para mim no Python 3 com o erro ImportError: No module named 'test.test_antigravity'devido a um conflito com o submódulo de teste da biblioteca mais unida. Talvez um especialista possa confirmar e alterar o nome do subdiretório de respostas para, por exemplo, 'testes' (plural).
expz 22/12/16

9
Meu test_antigravity.pyainda gera um erro de importação para ambos import antigravitye from antigravity import antigravitytambém. Eu tenho os dois __init_.pyarquivos e estou ligando python3 -m unittest discoverdo new projectdiretório. O que mais pode estar errado?
Imrek

19
arquivo test/__init__.pyé crucial aqui, mesmo vazios
François

3
@ Mike3d0g não tenho certeza se você quis dizer que o nome do diretório testé especial ... mas apenas para o registro, não é. : P python -m unittest discovertrabalha com arquivos de teste tests/tão bem quanto test/.
ryan

49

A solução mais simples para seus usuários é fornecer um script executável ( runtests.pyou algo parecido) que inicialize o ambiente de teste necessário, incluindo, se necessário, adicionando sys.pathtemporariamente o diretório do projeto raiz . Isso não requer que os usuários definam variáveis ​​de ambiente, algo assim funciona bem em um script de inicialização:

import sys, os

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

Então, suas instruções para seus usuários podem ser tão simples quanto " python runtests.py".

Obviamente, se o caminho que você realmente precisa é os.path.dirname(__file__), você não precisa adicioná-lo sys.path; O Python sempre coloca o diretório do script atualmente em execução no início de sys.path, portanto, dependendo da sua estrutura de diretórios, basta localizá-lo runtests.pyno lugar certo.

Além disso, o módulo mais unittest no Python 2.7+ (que é suportado como unittest2 para Python 2.6 e versões anteriores) agora possui a descoberta de teste incorporada; portanto, o nariz não é mais necessário se você desejar a descoberta automatizada de teste: suas instruções ao usuário podem ser tão simples quanto python -m unittest discover.


Eu coloquei alguns testes em uma subpasta como "Major Major". Eles podem ser executados com o python -m unittest discover, mas como posso optar por executar apenas um deles. Se eu executar python -m unittest tests / testxxxxx, ele falhará no problema do caminho. Desde dicovery modo de resolver tudo o que eu esperaria que não há outro truque para resolver problema de caminho sem handcoding correção caminho que sugerem no primeiro ponto
Frederic Bazin

2
@FredericBazin Não use o discovery se você quiser apenas um único teste ou arquivo de teste, apenas nomeie o módulo que deseja executar. Se você o nomear como um caminho pontilhado do módulo (em vez de um caminho de arquivo), ele poderá descobrir o caminho da pesquisa corretamente. Veja a resposta de Peter para mais detalhes.
Carl Meyer

Esse hack foi útil em um cenário em que eu tive que executar algo parecido python -m pdb tests\test_antigravity.py. Dentro do pdb, executei o sys.path.insert(0, "antigravity")que permitia que a instrução de importação resolvesse como se estivesse executando o módulo.
precisa saber é o seguinte

23

Geralmente, crio um script "executar testes" no diretório do projeto (aquele comum ao diretório de origem e test) que carrega meu conjunto "Todos os testes". Geralmente, esse é o código padrão, para que eu possa reutilizá-lo de um projeto para outro.

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 (de Como executo todos os testes de unidade Python em um diretório? )

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

Com esta configuração, você pode realmente apenas include antigravitynos seus módulos de teste. A desvantagem é que você precisaria de mais código de suporte para executar um teste específico ... Eu apenas os executava o tempo todo.


1
Eu também queria um run testsscript no diretório do projeto e encontrei uma maneira muito mais limpa de fazer isso. Altamente recomendado.
Z33k 26/0318

18

Do artigo ao qual você vinculou:

Crie um arquivo test_modulename.py e coloque seus testes mais unidos. Como os módulos de teste estão em um diretório separado do seu código, pode ser necessário adicionar o diretório pai do seu módulo ao seu PYTHONPATH para executá-los:

$ cd /path/to/googlemaps

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

$ python test/test_googlemaps.py

Finalmente, existe mais uma estrutura de teste de unidade popular para Python (é tão importante!), O nariz. O nose ajuda a simplificar e estender a estrutura unittest interna (por exemplo, ele pode encontrar automaticamente seu código de teste e configurar seu PYTHONPATH para você), mas não está incluído na distribuição padrão do Python.

Talvez você deva olhar o nariz, como sugere?


3
Sim, isso funciona (para mim), mas estou realmente pedindo as instruções mais simples que posso fornecer aos usuários do meu módulo para fazê-los executar os testes. Modificar o caminho pode ser realmente, mas estou buscando algo mais direto.
Major Major

4
Então, como é o seu caminho python depois de trabalhar em cem projetos? Devo entrar manualmente e limpar meu caminho? Se assim for, este é um design odioso!
precisa saber é o seguinte

11

Eu tive o mesmo problema, com uma pasta de testes de unidade separada. Das sugestões mencionadas, adiciono o caminho de origem absoluto para sys.path.

O benefício da seguinte solução é que é possível executar o arquivo test/test_yourmodule.pysem primeiro alterar o diretório de teste:

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

se você executar "python setup.py develop", o pacote estará no caminho. Mas você pode não querer fazer isso porque pode infectar a instalação do sistema python, e é por isso que existem ferramentas como virtualenv e buildout .


7

Solução / exemplo para o módulo unittest do Python

Dada a seguinte estrutura do projeto:

ProjectName
 ├── project_name
 |    ├── models
 |    |    └── thing_1.py
 |    └── __main__.py
 └── test
      ├── models
      |    └── test_thing_1.py
      └── __main__.py

Você pode executar seu projeto a partir do diretório raiz com o python project_nameque chama ProjectName/project_name/__main__.py.


Para executar seus testes python test, efetivamente executando ProjectName/test/__main__.py, você precisa fazer o seguinte:

1) Transforme seu test/modelsdiretório em um pacote, adicionando um __init__.pyarquivo. Isso torna os casos de teste dentro do subdiretório acessíveis a partir do testdiretório pai .

# ProjectName/test/models/__init__.py

from .test_thing_1 import Thing1TestCase        

2) Modifique o caminho do sistema test/__main__.pypara incluir o project_namediretório.

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

Agora você pode importar com sucesso itens de project_nameseus testes.

# 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

Use setup.py developpara tornar seu diretório de trabalho parte do ambiente Python instalado e, em seguida, execute os testes.


Isso me dá uma invalid command 'develop'e essa opção não é mencionada se eu pedir setup.py --help-commands. Precisa haver algo em setup.pysi para que isso funcione?
Major Major

Tudo bem - o problema era que estava faltando um import setuptoolsno meu setup.pyarquivo. Mas acho que isso mostra que isso não funcionará o tempo todo para os módulos de outras pessoas.
Major Major

1
Se você possui o pip , pode usá-lo para instalar o seu pacote no modo "editável" : pip install -e .Da mesma forma, o pacote é adicionado ao ambiente Python sem copiar a fonte, permitindo que você continue editando o local onde está.
Eric Smith

pip install -e .é exatamente a mesma coisa que python setup.py develop, apenas monitora o seu setup.pypara usar as ferramentas de configuração, mesmo que não funcione, de modo que funciona de qualquer maneira.
22814 Carl Meyer

5

Se você usa o VS Code e seus testes estão localizados no mesmo nível do seu projeto, a execução e a depuração do código não funcionam imediatamente. O que você pode fazer é alterar seu arquivo 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"
            ]
        }    
    ]
}

A linha principal aqui é envFile

"envFile": "${workspaceRoot}/.env",

Na raiz do seu projeto, adicione o arquivo .env

Dentro do seu arquivo .env, adicione o caminho à raiz do seu projeto. Isso adicionará temporariamente

PYTHONPATH = C: \ SEU \ PYTHON \ PROJETO \ ROOT_DIRECTORY

caminho para o seu projeto e você poderá usar testes de unidade de depuração do VS Code


5

Notei que, se você executar a interface de linha de comando mais unida do diretório "src", as importações funcionarão corretamente sem modificações.

python -m unittest discover -s ../test

Se você deseja colocar isso em um arquivo em lotes no diretório do projeto, pode fazer o seguinte:

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

5

Eu tenho o mesmo problema há muito tempo. O que eu escolhi recentemente é a seguinte estrutura de diretórios:

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

e no __init__.pyscript da pasta de teste, escrevo o seguinte:

import os
import sys
PROJECT_PATH = os.getcwd()
SOURCE_PATH = os.path.join(
    PROJECT_PATH,"src"
)
sys.path.append(SOURCE_PATH)

Super importante para compartilhar o projeto é o Makefile, porque ele impõe a execução correta dos scripts. Aqui está o comando que eu coloquei no Makefile:

run_tests:
    python -m unittest discover .

O Makefile é importante não apenas por causa do comando que é executado, mas também por causa de onde ele é executado . Se você cdasse os testes e fizesse python -m unittest discover .isso, não funcionaria porque o script init em unit_tests chama os.getcwd (), que apontaria para o caminho absoluto incorreto (que seria anexado ao sys.path e você estaria ausente sua pasta de origem). Os scripts seriam executados desde que o Discover encontre todos os testes, mas eles não seriam executados corretamente. Portanto, o Makefile está lá para evitar ter que se lembrar desse problema.

Eu realmente gosto dessa abordagem porque não preciso tocar na minha pasta src, nos meus testes de unidade ou nas minhas variáveis ​​de ambiente e tudo funciona sem problemas.

Deixe-me saber se vocês gostam.

Espero que ajude,


4

A seguir está a estrutura do meu projeto:

ProjectFolder:
 - project:
     - __init__.py
     - item.py
 - tests:
     - test_item.py

Achei melhor importar no método 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

Qual é a maneira usual de realmente executar os testes

Eu uso o Python 3.6.2

cd new_project

pytest test/test_antigravity.py

Para instalar o pytest :sudo pip install pytest

Não defini nenhuma variável de caminho e minhas importações não estão falhando com a mesma estrutura de projeto de "teste".

Eu comentei essas coisas: if __name__ == '__main__'assim:

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

É possível usar o wrapper que executa testes selecionados ou todos os testes.

Por exemplo:

./run_tests antigravity/*.py

ou para executar todos os testes, use recursivamente globbing ( tests/**/*.py) (ativar por shopt -s globstar).

O wrapper pode basicamente usar argparsepara analisar os argumentos como:

parser = argparse.ArgumentParser()
parser.add_argument('files', nargs='*')

Em seguida, carregue todos os testes:

for filename in args.files:
    exec(open(filename).read())

adicione-os ao seu conjunto de testes (usando 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))

e execute-os:

result = unittest.TextTestRunner(verbosity=2).run(alltests)

Veja este exemplo para mais detalhes.

Consulte também: Como executar todos os testes de unidade Python em um diretório?


4

Python 3+

Adicionando a @Pierre

Usando uma unittestestrutura de diretórios como esta:

new_project
├── antigravity
   ├── __init__.py         # make it a package
   └── antigravity.py
└── test
    ├── __init__.py         # also make test a package
    └── test_antigravity.py

Para executar o módulo de teste test_antigravity.py:

$ cd new_project
$ python -m unittest test.test_antigravity

Ou um único TestCase

$ python -m unittest test.test_antigravity.GravityTestCase

Obrigatório , não esqueça que, __init__.pymesmo que vazio, caso contrário não funcionará.


2

Você não pode importar do diretório pai sem algum vodu. Aqui está outra maneira de trabalhar com pelo menos Python 3.6.

Primeiro, tenha um arquivo test / context.py com o seguinte conteúdo:

import sys
import os
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))

Em seguida, faça a seguinte importação no arquivo test / test_antigravity.py:

import unittest
try:
    import context
except ModuleNotFoundError:
    import test.context    
import antigravity

Observe que o motivo dessa cláusula try-except é que

  • importação test.context falha quando executado com "python test_antigravity.py" e
  • O contexto de importação falha quando executado com "python -m unittest" no diretório new_project.

Com esse truque, ambos trabalham.

Agora você pode executar todos os arquivos de teste no diretório de teste com:

$ pwd
/projects/new_project
$ python -m unittest

ou execute um arquivo de teste individual com:

$ cd test
$ python test_antigravity

Ok, não é muito mais bonito do que ter o conteúdo de context.py em test_antigravity.py, mas talvez um pouco. Sugestões são bem vindas.


2

Se você tiver vários diretórios em seu diretório de teste, precisará adicionar a cada diretório um __init__.pyarquivo.

/home/johndoe/snakeoil
└── test
    ├── __init__.py        
    └── frontend
        └── __init__.py
        └── test_foo.py
    └── backend
        └── __init__.py
        └── test_bar.py

Em seguida, para executar todos os testes de uma vez, execute:

python -m unittest discover -s /home/johndoe/snakeoil/test -t /home/johndoe/snakeoil

Fonte: 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

Esse script BASH executará o diretório de teste unittest python de qualquer lugar do sistema de arquivos, independentemente do diretório de trabalho em que você esteja.

Isso é útil ao permanecer no diretório ./srcou no ./exampletrabalho e você precisa de um teste de unidade rápido:

#!/bin/bash

this_program="$0"
dirname="`dirname $this_program`"
readlink="`readlink -e $dirname`"

python -m unittest discover -s "$readlink"/test -v

Não há necessidade de um test/__init__.pyarquivo sobrecarregar seu pacote / sobrecarga de memória durante a produção.


1

Dessa forma, você poderá executar os scripts de teste de onde quiser, sem mexer nas variáveis ​​do sistema na linha de comando.

Isso adiciona a pasta principal do projeto ao caminho do python, com o local encontrado em relação ao próprio script, não em relação ao diretório de trabalho atual.

import sys, os

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

Adicione isso ao topo de todos os seus scripts de teste. Isso adicionará a pasta principal do projeto ao caminho do sistema, para que qualquer módulo importado que trabalhe a partir daí agora funcione. E não importa de onde você executa os testes.

Obviamente, você pode alterar o arquivo project_path_hack para corresponder ao local da pasta principal do projeto.


0

Se você está procurando uma solução somente de linha de comando:

Com base na seguinte estrutura de diretórios (generalizada com um diretório de origem dedicado):

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

Windows : (pol new_project)

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

Veja esta pergunta se você deseja usá-lo em um loop for batch.

Linux : (in 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

Com essa abordagem, também é possível adicionar mais diretórios ao PYTHONPATH, se necessário.


0

Você realmente deve usar a ferramenta pip.

Use pip install -e .para instalar seu pacote no modo de desenvolvimento. Essa é uma prática muito boa, recomendada pelo pytest (consulte a documentação de boas práticas , onde você também pode encontrar dois layouts de projeto a seguir).


Por que reduzir a votação desta resposta? Li a resposta aceita e, embora não tenha sido ruim, pytesté muito melhor executar testes, devido à saída do console que você obtém, em cores, com informações de rastreamento de pilha e informações detalhadas sobre erros de asserção.
aliopi
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.