Como executo todos os testes de unidade Python em um diretório?


315

Eu tenho um diretório que contém meus testes de unidade Python. Cada módulo de teste de unidade possui o formato test _ *. Py . Estou tentando criar um arquivo chamado all_test.py que, você adivinhou, executará todos os arquivos no formulário de teste mencionado acima e retornará o resultado. Eu tentei dois métodos até agora; ambos falharam. Vou mostrar os dois métodos e espero que alguém lá fora saiba como fazer isso corretamente.

Para minha primeira tentativa valente, pensei: "Se eu importar todos os meus módulos de teste no arquivo e chamar esse unittest.main()doodad, ele funcionará, certo?" Bem, acontece que eu estava errado.

import glob
import unittest

testSuite = unittest.TestSuite()
test_file_strings = glob.glob('test_*.py')
module_strings = [str[0:len(str)-3] for str in test_file_strings]

if __name__ == "__main__":
     unittest.main()

Isso não funcionou, o resultado que obtive foi:

$ python all_test.py 

----------------------------------------------------------------------
Ran 0 tests in 0.000s

OK

Na minha segunda tentativa, eu acho que talvez eu tente fazer todo esse teste de uma maneira mais "manual". Então, tentei fazer isso abaixo:

import glob
import unittest

testSuite = unittest.TestSuite()
test_file_strings = glob.glob('test_*.py')
module_strings = [str[0:len(str)-3] for str in test_file_strings]
[__import__(str) for str in module_strings]
suites = [unittest.TestLoader().loadTestsFromName(str) for str in module_strings]
[testSuite.addTest(suite) for suite in suites]
print testSuite 

result = unittest.TestResult()
testSuite.run(result)
print result

#Ok, at this point I have a result
#How do I display it as the normal unit test command line output?
if __name__ == "__main__":
    unittest.main()

Isso também não funcionou, mas parece tão próximo!

$ python all_test.py 
<unittest.TestSuite tests=[<unittest.TestSuite tests=[<unittest.TestSuite tests=[<test_main.TestMain testMethod=test_respondes_to_get>]>]>]>
<unittest.TestResult run=1 errors=0 failures=0>

----------------------------------------------------------------------
Ran 0 tests in 0.000s

OK

Parece que tenho algum tipo de suíte e posso executar o resultado. Estou um pouco preocupado com o fato de que ele diz que só tenho run=1, parece que deveria ser run=2, mas é progresso. Mas como passo e mostro o resultado para main? Ou como eu basicamente o faço funcionar para que eu possa executar este arquivo e, ao fazê-lo, executar todos os testes de unidade neste diretório?


1
Pule para a resposta do Travis, se você estiver usando o Python 2.7+
Rocky

você já tentou executar os testes de um objeto de instância de teste?
Pinocchio

Veja esta resposta para uma solução com uma estrutura de arquivo de exemplo.
Derek Soike

Respostas:


477

Com o Python 2.7 e superior, você não precisa escrever um novo código ou usar ferramentas de terceiros para fazer isso; a execução recursiva do teste via linha de comando é incorporada. Coloque um __init__.pyem seu diretório de teste e:

python -m unittest discover <test_directory>
# or
python -m unittest discover -s <directory> -p '*_test.py'

Você pode ler mais na documentação unittest do python 2.7 ou python 3.x.


11
Os problemas incluem: ImportError: O diretório inicial não é importável:
zinking

6
Pelo menos com o Python 2.7.8 no Linux, nenhuma chamada de linha de comando me fornece recursão. Meu projeto possui vários subprojetos cujos testes de unidade residem nos respectivos diretórios "unit_tests / <subproject> / python /". Se eu especificar esse caminho, os testes de unidade para esse subprojeto serão executados, mas com apenas "unit_tests" como argumento do diretório de teste, nenhum teste será encontrado (em vez de todos os testes para todos os subprojetos, como eu esperava). Alguma dica?
user686249

6
Sobre a recursão: O primeiro comando sem um <diretório_teste> é padronizado como "." e se repete em submódulos . Ou seja, todos os diretórios de teste que você deseja descobrir precisam ter um init .py. Se o fizerem, serão encontrados pelo comando discover. Apenas tentei, funcionou.
Emil Stenström

Isso funcionou para mim. Eu tenho uma pasta de testes com quatro arquivos, execute isso no meu terminal Linux, ótimas coisas.
JasTonAChair 22/09

5
Obrigado! Por que essa não é a resposta aceita? Em minha opinião, a melhor resposta é sempre a única que não requer quaisquer dependências externas ...
Jonathan Benn

108

Você poderia usar um executor de teste que faria isso por você. nariz é muito bom, por exemplo. Quando executado, ele encontra testes na árvore atual e os executa.

Atualizada:

Aqui está um código dos meus dias anteriores ao nariz. Você provavelmente não deseja a lista explícita de nomes de módulos, mas talvez o restante seja útil para você.

testmodules = [
    'cogapp.test_makefiles',
    'cogapp.test_whiteutils',
    'cogapp.test_cogapp',
    ]

suite = unittest.TestSuite()

for t in testmodules:
    try:
        # If the module defines a suite() function, call it to get the suite.
        mod = __import__(t, globals(), locals(), ['suite'])
        suitefn = getattr(mod, 'suite')
        suite.addTest(suitefn())
    except (ImportError, AttributeError):
        # else, just load all the test cases from the module.
        suite.addTest(unittest.defaultTestLoader.loadTestsFromName(t))

unittest.TextTestRunner().run(suite)

2
A vantagem dessa abordagem é apenas importar explicitamente todos os seus módulos de teste para um módulo test_all.py e chamar unittest.main () que você pode opcionalmente declarar um conjunto de testes em alguns módulos e não em outros?
Corey Porter

1
Eu experimentei o nariz e funciona perfeitamente. Foi fácil de instalar e executar no meu projeto. Eu até consegui automatizá-lo com algumas linhas de script, executando dentro de um virtualenv. +1 para nariz!
Jesse Webb

Nem sempre é possível: às vezes, a importação da estrutura do projeto pode fazer com que o nariz fique confuso se tentar executar as importações nos módulos.
Chiffa

4
Observe que o nariz está "no modo de manutenção nos últimos anos" e atualmente é recomendável usar o nose2 , o pytest ou apenas o unittest / unittest2 para novos projetos.
Kurt Peek #

você já tentou executar os testes de um objeto de instância de teste?
Pinocchio

96

No python 3, se você estiver usando unittest.TestCase:

  • Você deve ter um __init__.pyarquivo vazio (ou não) em seu testdiretório ( deve ser nomeado test/)
  • Seus arquivos de teste dentro test/desse padrão correspondem ao padrão test_*.py. Eles podem estar dentro de um subdiretório em test/e esses subdiretórios podem ser nomeados como qualquer coisa.

Em seguida, você pode executar todos os testes com:

python -m unittest

Feito! Uma solução com menos de 100 linhas. Espero que outro iniciante em python economize tempo encontrando isso.


3
Observe que, por padrão, ele procura apenas testes em nomes de arquivos que começam com "test" #
616 Shawabawa

3
Está correto, a pergunta original se referia ao fato de que "Cada módulo de teste de unidade possui o formato test _ *. Py.", Portanto, essa resposta é respondida diretamente. Eu já atualizei a resposta ser mais explícito
tmck-code

1
Obrigado, o que faltava para eu usar a resposta de Travis Bear.
Jeremy Cochoy 07/12/19

65

Agora isso é possível diretamente do unittest: unittest.TestLoader.discover .

import unittest
loader = unittest.TestLoader()
start_dir = 'path/to/your/test/files'
suite = loader.discover(start_dir)

runner = unittest.TextTestRunner()
runner.run(suite)

3
Eu tentei esse método também, tenho alguns testes, mas funciona perfeitamente. Excelente!!! Mas estou curioso, tenho apenas 4 testes. Juntos, eles executam 0.032s, mas quando eu uso esse método para executá-los todos, obtém resultado .... ---------------------------------------------------------------------- Ran 4 tests in 0.000s OKPor que? A diferença, de onde vem?
Simkus

Estou tendo problemas para executar um arquivo que se parece com isso na linha de comando. Como deve ser invocado?
Dustin Michels

python file.py
slaughter98

1
Trabalhou na perfeição! Basta configurá-lo em seu test / dir e depois definir o start_id = "./". IMHO, esta resposta é agora (Python 3.7) o caminho aceito!
Jjwdesign 17/06/19

Você pode alterar a última linha para res = runner.run (suite); sys.exit (0 se res.wasSuccessful () else 1) ´ se você deseja um código de saída correto
Sadap

32

Bem, estudando o código acima um pouco (especificamente usando TextTestRunnere defaultTestLoader), consegui me aproximar bastante. Eventualmente, eu corrigi meu código passando apenas todos os conjuntos de testes para um único construtor de conjuntos, em vez de adicioná-los "manualmente", o que corrigia meus outros problemas. Então aqui está a minha solução.

import glob
import unittest

test_files = glob.glob('test_*.py')
module_strings = [test_file[0:len(test_file)-3] for test_file in test_files]
suites = [unittest.defaultTestLoader.loadTestsFromName(test_file) for test_file in module_strings]
test_suite = unittest.TestSuite(suites)
test_runner = unittest.TextTestRunner().run(test_suite)

Sim, provavelmente é mais fácil usar o nariz do que fazer isso, mas isso está além do ponto.


bom, funciona bem para o diretório atual, como invocar o sub-diretamente?
Larry Cai

Larry, ver a nova resposta ( stackoverflow.com/a/24562019/104143 ) para a descoberta de teste recursivo
Peter Kofler

você já tentou executar os testes de um objeto de instância de teste?
Pinocchio

25

Se você deseja executar todos os testes de várias classes de casos de teste e pode especificá-los explicitamente, é possível fazer o seguinte:

from unittest import TestLoader, TextTestRunner, TestSuite
from uclid.test.test_symbols import TestSymbols
from uclid.test.test_patterns import TestPatterns

if __name__ == "__main__":

    loader = TestLoader()
    tests = [
        loader.loadTestsFromTestCase(test)
        for test in (TestSymbols, TestPatterns)
    ]
    suite = TestSuite(tests)

    runner = TextTestRunner(verbosity=2)
    runner.run(suite)

onde uclidé meu projeto e TestSymbolse TestPatternssão subclasses de TestCase.


A partir dos docs unittest.TestLoader : "Normalmente, não há necessidade de criar uma instância dessa classe; o módulo unittest fornece uma instância que pode ser compartilhado como unittest.defaultTestLoader." Além disso, como TestSuiteaceita um iterável como argumento, você pode construir o iterável em um loop para evitar repetições loader.loadTestsFromTestCase.
Alquimista de dois bits

@ Alquimista de dois bits, seu segundo ponto em particular é bom. Eu mudaria o código para incluir, mas não posso testá-lo. (O primeiro mod faria com que parecesse muito com Java para o meu gosto ... embora eu perceba que estou sendo irracional (que se dane-os e os nomes de variáveis ​​dos casos de camelo)).
ouriço demente,

Este é o meu favorito, muito limpo. Consegui empacotar isso e torná-lo um argumento na minha linha de comando regular.
MarkII

15

Eu usei o discovermétodo e uma sobrecarga de load_testspara alcançar esse resultado em um número mínimo de linhas de código:

def load_tests(loader, tests, pattern):
''' Discover and load all unit tests in all files named ``*_test.py`` in ``./src/``
'''
    suite = TestSuite()
    for all_test_suite in unittest.defaultTestLoader.discover('src', pattern='*_tests.py'):
        for test_suite in all_test_suite:
            suite.addTests(test_suite)
    return suite

if __name__ == '__main__':
    unittest.main()

Execução em cinco algo como

Ran 27 tests in 0.187s
OK

este está disponível apenas para python2.7, eu acho
Larry Cai

@larrycai Talvez eu esteja normalmente no Python 3, às vezes Python 2.7. A questão não estava vinculada a uma versão específica.
quer

Estou no Python 3.4 e descubra retorna uma suíte, tornando o loop redundante.
Dunes

Para o futuro Larry: "Muitos novos recursos foram adicionados ao unittest no Python 2.7, incluindo a descoberta de testes. O unittest2 permite que você use esses recursos com versões anteriores do Python."
Alquimista de dois bits

8

Tentei várias abordagens, mas todas parecem defeituosas ou tenho que criar algum código, isso é irritante. Mas há uma maneira conveniente no linux, que é simplesmente encontrar todos os testes através de determinado padrão e depois invocá-los um a um.

find . -name 'Test*py' -exec python '{}' \;

e o mais importante, definitivamente funciona.


7

No caso de uma biblioteca ou aplicativo empacotado , você não deseja fazê-lo. setuptools fará isso por você .

Para usar esse comando, os testes do seu projeto devem ser agrupados em um unittestconjunto de testes por uma função, uma classe ou método TestCase ou um módulo ou pacote contendo TestCaseclasses. Se o conjunto nomeado for um módulo e o módulo tiver uma additional_tests()função, ele será chamado e o resultado (que deve ser a unittest.TestSuite) será adicionado aos testes a serem executados. Se o conjunto nomeado for um pacote, quaisquer sub-módulos e subpacotes serão adicionados recursivamente ao conjunto de testes geral .

Apenas diga onde está o seu pacote de teste raiz, como:

setup(
    # ...
    test_suite = 'somepkg.test'
)

E corra python setup.py test.

A descoberta baseada em arquivo pode ser problemática no Python 3, a menos que você evite importações relativas no seu conjunto de testes, porque discoverusa a importação de arquivos. Embora ele suporte opcional top_level_dir, mas eu tive alguns erros de recursão infinitos. Portanto, uma solução simples para um código não empacotado é colocar o seguinte em__init__.py seu pacote de teste (consulte o protocolo load_tests ).

import unittest

from . import foo, bar


def load_tests(loader, tests, pattern):
    suite = unittest.TestSuite()
    suite.addTests(loader.loadTestsFromModule(foo))
    suite.addTests(loader.loadTestsFromModule(bar))

    return suite

Boa resposta e pode ser usado para automatizar o teste antes da implantação! Obrigado
Arthur Clerc-Gherardi

4

Eu uso o PyDev / LiClipse e ainda não descobri como executar todos os testes de uma só vez a partir da GUI. (editar: você clica com o botão direito do mouse na pasta de teste raiz e escolheRun as -> Python unit-test

Esta é minha solução atual:

import unittest

def load_tests(loader, tests, pattern):
    return loader.discover('.')

if __name__ == '__main__':
    unittest.main()

Eu coloquei esse código em um módulo chamado all no meu diretório de teste. Se eu executar este módulo como o mais unittest do LiClipse, todos os testes serão executados. Se eu pedir para repetir apenas testes específicos ou com falha, apenas esses testes serão executados. Também não interfere no meu executor de teste da linha de comando (testes nos) - é ignorado.

Pode ser necessário alterar os argumentos para com discoverbase na configuração do seu projeto.


Os nomes de todos os arquivos e métodos de teste devem começar com "test_". Caso contrário, o comando "Executar como -> teste de unidade Python" não os encontrará.
Stefan

2

Com base na resposta de Stephen Cagle , adicionei suporte para módulos de teste aninhados.

import fnmatch
import os
import unittest

def all_test_modules(root_dir, pattern):
    test_file_names = all_files_in(root_dir, pattern)
    return [path_to_module(str) for str in test_file_names]

def all_files_in(root_dir, pattern):
    matches = []

    for root, dirnames, filenames in os.walk(root_dir):
        for filename in fnmatch.filter(filenames, pattern):
            matches.append(os.path.join(root, filename))

    return matches

def path_to_module(py_file):
    return strip_leading_dots( \
        replace_slash_by_dot(  \
            strip_extension(py_file)))

def strip_extension(py_file):
    return py_file[0:len(py_file) - len('.py')]

def replace_slash_by_dot(str):
    return str.replace('\\', '.').replace('/', '.')

def strip_leading_dots(str):
    while str.startswith('.'):
       str = str[1:len(str)]
    return str

module_names = all_test_modules('.', '*Tests.py')
suites = [unittest.defaultTestLoader.loadTestsFromName(mname) for mname 
    in module_names]

testSuite = unittest.TestSuite(suites)
runner = unittest.TextTestRunner(verbosity=1)
runner.run(testSuite)

O código de pesquisa todos os subdiretórios .para *Tests.pyarquivos que são então carregados. Espera que cada *Tests.pyum contenha uma única classe *Tests(unittest.TestCase)que é carregada por vez e executada uma após a outra.

Isso funciona com o aninhamento profundo arbitrário de diretórios / módulos, mas cada diretório deve conter __init__.pypelo menos um arquivo vazio . Isso permite que o teste carregue os módulos aninhados substituindo barras (ou barras invertidas) por pontos (consulte replace_slash_by_dot).


2

Esta é uma pergunta antiga, mas o que funcionou para mim agora (em 2019) é:

python -m unittest *_test.py

Todos os meus arquivos de teste estão na mesma pasta que os arquivos de origem e terminam com _test.



1

Esse script BASH executará o diretório de teste python unittest de QUALQUER LUGAR no sistema de arquivos, independentemente do diretório de trabalho em que você esteja: seu diretório de trabalho sempre estará onde esse testdiretório está localizado.

TODOS OS TESTES, independentes $ PWD

O módulo Python mais unido é sensível ao seu diretório atual, a menos que você diga onde (usando a discover -sopção).

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

#!/bin/bash
this_program="$0"
dirname="`dirname $this_program`"
readlink="`readlink -e $dirname`"

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

TESTES SELECIONADOS, $ PWD independentes

Eu nomeio este arquivo utilitário: runone.pye use-o assim:

runone.py <test-python-filename-minus-dot-py-fileextension>
#!/bin/bash
this_program="$0"
dirname="`dirname $this_program`"
readlink="`readlink -e $dirname`"

(cd "$dirname"/test; python -m unittest $1)

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


-3

Aqui está minha abordagem criando um wrapper para executar testes na linha de comando:

#!/usr/bin/env python3
import os, sys, unittest, argparse, inspect, logging

if __name__ == '__main__':
    # Parse arguments.
    parser = argparse.ArgumentParser(add_help=False)
    parser.add_argument("-?", "--help",     action="help",                        help="show this help message and exit" )
    parser.add_argument("-v", "--verbose",  action="store_true", dest="verbose",  help="increase output verbosity" )
    parser.add_argument("-d", "--debug",    action="store_true", dest="debug",    help="show debug messages" )
    parser.add_argument("-h", "--host",     action="store",      dest="host",     help="Destination host" )
    parser.add_argument("-b", "--browser",  action="store",      dest="browser",  help="Browser driver.", choices=["Firefox", "Chrome", "IE", "Opera", "PhantomJS"] )
    parser.add_argument("-r", "--reports-dir", action="store",   dest="dir",      help="Directory to save screenshots.", default="reports")
    parser.add_argument('files', nargs='*')
    args = parser.parse_args()

    # Load files from the arguments.
    for filename in args.files:
        exec(open(filename).read())

    # See: http://codereview.stackexchange.com/q/88655/15346
    def make_suite(tc_class):
        testloader = unittest.TestLoader()
        testnames = testloader.getTestCaseNames(tc_class)
        suite = unittest.TestSuite()
        for name in testnames:
            suite.addTest(tc_class(name, cargs=args))
        return suite

    # Add all tests.
    alltests = unittest.TestSuite()
    for name, obj in inspect.getmembers(sys.modules[__name__]):
        if inspect.isclass(obj) and name.startswith("FooTest"):
            alltests.addTest(make_suite(obj))

    # Set-up logger
    verbose = bool(os.environ.get('VERBOSE', args.verbose))
    debug   = bool(os.environ.get('DEBUG', args.debug))
    if verbose or debug:
        logging.basicConfig( stream=sys.stdout )
        root = logging.getLogger()
        root.setLevel(logging.INFO if verbose else logging.DEBUG)
        ch = logging.StreamHandler(sys.stdout)
        ch.setLevel(logging.INFO if verbose else logging.DEBUG)
        ch.setFormatter(logging.Formatter('%(asctime)s %(levelname)s: %(name)s: %(message)s'))
        root.addHandler(ch)
    else:
        logging.basicConfig(stream=sys.stderr)

    # Run tests.
    result = unittest.TextTestRunner(verbosity=2).run(alltests)
    sys.exit(not result.wasSuccessful())

Por uma questão de simplicidade, desculpe meus padrões de codificação não- PEP8 .

Em seguida, você pode criar a classe BaseTest para componentes comuns para todos os seus testes, para que cada um deles se pareça com:

from BaseTest import BaseTest
class FooTestPagesBasic(BaseTest):
    def test_foo(self):
        driver = self.driver
        driver.get(self.base_url + "/")

Para executar, basta especificar testes como parte dos argumentos da linha de comando, por exemplo:

./run_tests.py -h http://example.com/ tests/**/*.py

2
a maior parte dessa resposta não tem nada a ver com a descoberta de teste (por exemplo, registro etc.). O estouro de pilha é para responder a perguntas, não exibindo código não relacionado.
Corey Goldberg
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.