Pergunta

Eu tenho um diretório que contém meus testes de unidade Python. Cada módulo de teste de unidade é do formulário teste _*. py. Estou tentando fazer um arquivo chamado all_test.py Isso, você adivinhou, executará todos os arquivos no formulário de teste acima mencionado e retornará o resultado. Eu tentei dois métodos até agora; Ambos falharam. Vou mostrar os dois métodos e espero que alguém por aí saiba como realmente fazer isso corretamente.

Para minha primeira tentativa valente, pensei "se eu apenas importar todos os meus módulos de teste no arquivo, e depois chame isso unittest.main() Doodad, vai 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

Para minha segunda tentativa, eu, ok, talvez eu tente fazer toda essa coisa de teste de uma maneira mais "manual". Então eu 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 perto!

$ 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 dizer que só tenho run=1, parece que deveria ser run=2, mas é progresso. Mas como faço para passar e exibir o resultado para Main? Ou como faço para que eu funcione basicamente para que eu possa executar esse arquivo e, ao fazer isso, executar todos os testes de unidade neste diretório?

Foi útil?

Solução

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

Atualizada:

Aqui está algum código dos meus dias antes do nariz. Você provavelmente não deseja a lista explícita de nomes de módulos, mas talvez o resto 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)

Outras dicas

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 de teste recursiva por meio da linha de comando está embutida.

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

Você pode ler mais no Python 2.7ou Python 3.x Documentação mais unitter.

Isso agora é 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)

Em Python 3, se você está usando unittest.TestCase:

  • Você deve ter um vazio (ou não) __init__.py arquivo em seu test diretório (devo ser nomeado test/)
  • Seus arquivos de teste dentro test/ Combine o padrão test_*.py. Eles podem estar dentro de um subdiretório sob test/, e esses subdirs podem ser nomeados como qualquer coisa.

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

python -m unittest

Feito! Uma solução inferior a 100 linhas. Espero que outro iniciante em Python economize tempo, encontrando isso.

Bem estudando um pouco o código acima (especificamente usando TextTestRunner e defaultTestLoader), Pude chegar bem perto. Eventualmente, consertei meu código também passando todas as suítes de teste para um único construtor de suítes, em vez de adicioná -los "manualmente", que corrigiram meus outros problemas. Então aqui está 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.

Se você deseja executar todos os testes de várias classes de casos de teste e ficará feliz em especificá -las explicitamente, pode fazer assim:

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 TestSymbols e TestPatterns são subclasses de TestCase.

Eu usei o discover método e uma sobrecarga de load_tests Para alcançar esse resultado em um número (mínimo, eu acho) 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 como algo como

Ran 27 tests in 0.187s
OK

Eu tentei várias abordagens, mas todas parecem falhas ou tenho que maquinar algum código, isso é irritante. Mas há uma maneira conveiniente do Linux, que é simplesmente encontrar todos os testes através de certos padrão e depois invocá -los um por um.

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

E o mais importante, definitivamente funciona.

Em caso de um embalado Biblioteca ou aplicativo, você não deseja fazer isso. setuptools vai fazer isso por você.

Para usar este comando, os testes do seu projeto devem ser embrulhados em um unittest suíte de teste por uma função, uma classe de teste ou método ou um módulo ou pacote que contém TestCase Aulas. Se a suíte nomeada é um módulo, e o módulo tem um additional_tests() função, é chamado e o resultado (que deve ser um unittest.TestSuite) é adicionado aos testes a serem executados. Se a suíte nomeada for um pacote, Quaisquer submódulos e subpackagens são recorrentes adicionados ao conjunto geral de testes.

Basta dizer onde está o seu pacote de teste raiz, como:

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

E corra python setup.py test.

A descoberta baseada em arquivos pode ser problemática no Python 3, a menos que você evite as importações relativas em seu conjunto de testes, porque discover usa a importação de arquivos. Mesmo suportando opcional top_level_dir, mas eu tinha alguns erros de recursão infinitos. Portanto, uma solução simples para um código não embalado é colocar o seguinte __init__.py do seu pacote de teste (veja 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

Eu uso Pydev/LiClipse e realmente 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 escolhe Run as -> Python unit-test

Esta é a minha solução alternativa atual:

import unittest

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

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

Eu coloquei este código em um módulo chamado all no meu diretório de teste. Se eu executar este módulo como o mais unitter 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 Runner de teste de comando (Nosetests) - é ignorado.

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

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 pesquisa todos os subdiretos de . por *Tests.py arquivos que são então carregados. Espera cada um *Tests.py Para conter uma única classe *Tests(unittest.TestCase) que é carregado por sua vez e executado um após o outro.

Isso funciona com um ninho profundo arbitrário de diretórios/módulos, mas cada diretório entre precisa conter um vazio __init__.py arquivo pelo menos. Isso permite que o teste carregue os módulos aninhados, substituindo barras (ou barras de barriga) por pontos (ver replace_slash_by_dot).

Como a descoberta de testes parece ser um assunto completo, há alguma estrutura dedicada para testar a descoberta:

Mais leitura aqui: https://wiki.python.org/moin/pythontestingToolStaxonomy

Este script bash executará o diretório de teste Python Unittest de qualquer lugar do sistema de arquivos, independentemente do diretório de trabalho em que você está: seu diretório de trabalho sempre estará onde isso test diretório está localizado.

Todos os testes, independente $ PWD

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

Isso é útil ao permanecer no ./src ou ./example Diretório de trabalho e você precisa de um rápido teste de unidade geral:

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

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

Testes selecionados, independente $ PWD

Eu nomeio este arquivo de utilitário: runone.py e use 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__.py Arquivo para sobrecarregar seu pacote/cabeça de memória durante a produção.

Aqui está minha abordagem criando um invólucro 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 meu nãoPep8 padrões de codificação.

Em seguida, você pode criar a classe Basetest para componentes comuns para todos os seus testes, para que cada um dos seus testes fosse simplesmente:

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

Para executar, você simplesmente especifica os testes como parte dos argumentos da linha de comando, por exemplo:

./run_tests.py -h http://example.com/ tests/**/*.py
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top