Tüm Python birimi testlerini bir dizinde nasıl çalıştırabilirim?


315

Benim Python birim testleri içeren bir dizin var. Her birim test modülü _ *. Py form testindedir . Ben , tahmin ettiğiniz gibi, yukarıda belirtilen test formundaki tüm dosyaları çalıştırmak ve sonucu döndürecek all_test.py adlı bir dosya yapmaya çalışıyorum . Şimdiye kadar iki yöntem denedim; ikisi de başarısız oldu. İki yöntemi göstereceğim ve umarım dışarıdaki biri bunu nasıl doğru yapacağını bilir.

İlk cesur girişimim için, "Tüm test modüllerimi sadece dosyaya unittest.main()aktarır ve sonra bu doodad'ı çağırırsam, işe yarayacak, değil mi?" Diye düşündüm. Görünüşe göre yanılmışım.

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

Bu işe yaramadı, elde ettiğim sonuç:

$ python all_test.py 

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

OK

İkinci denemem için, tamam, belki de tüm bu test işlemini daha "manuel" bir şekilde yapmaya çalışacağım. Bu yüzden aşağıda yapmaya çalıştım:

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

Bu da işe yaramadı, ama çok yakın görünüyor!

$ 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

Bir çeşit paketim var ve sonucu yürütebilirim. Sadece sahip olduğumu söylemesi gerçeğiyle ilgili biraz endişeliyim, olması run=1gerektiği gibi görünüyor run=2, ancak ilerleme. Ama sonucu anaya nasıl iletirim? Ya da sadece bu dosyayı çalıştırabilmem için nasıl çalıştırabilirim ve bunu yaparken, bu dizindeki tüm birim testlerini çalıştırabilir miyim?


1
Python 2.7+
Rocky

hiç bir test örneği nesnesinden testleri çalıştırmayı denediniz mi?
Pinokyo

Örnek dosya yapısına sahip bir çözüm için bu cevaba bakınız .
Derek Soike

Yanıtlar:


477

Python 2.7 ve daha yeni sürümlerde bunu yapmak için yeni kod yazmak veya üçüncü taraf araçlar kullanmak zorunda değilsiniz; komut satırı üzerinden yinelemeli test yürütme yerleşiktir. __init__.pyTest dizininize bir kod koyun ve:

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

Daha fazla bilgiyi python 2.7 veya python 3.x unittest belgelerinde okuyabilirsiniz .


11
sorunlar şunlardır: ImportError: Başlangıç ​​dizini alınamaz:
zinking

6
En azından Linux üzerinde Python 2.7.8 ile hiçbir komut satırı çağırma bana özyineleme vermez. Projemde birim testleri ilgili "unit_tests / <subproject> / python /" dizinlerinde bulunan birkaç alt proje var. Böyle bir yol belirtirseniz, bu alt proje için birim testleri çalıştırılır, ancak yalnızca "unit_tests" ile test dizini argümanı olarak hiçbir test bulunamaz (umduğum gibi tüm alt projeler için tüm testler yerine). İpucu var mı?
user686249

6
Özyineleme hakkında: <test_directory> içermeyen ilk komut varsayılan olarak "." ve recurses submodüller . Yani, keşfedilmesini istediğiniz tüm test dizinlerinin init .py'ye sahip olması gerekir . Eğer yaparlarsa, find komutuyla bulunurlar. Sadece denedim, işe yaradı.
Emil Stenström

Bu benim için çalıştı. Dört dosyalı bir test klasörü var, bunu Linux terminalimden çalıştır, harika şeyler.
JasTonAChair

5
Teşekkürler! Bu neden kabul edilen cevap değil? Bana göre, daha iyi cevap her zaman dış bağımlılık gerektirmeyen cevaptır ...
Jonathan Benn

108

Bunu sizin için yapacak bir test koşucusu kullanabilirsiniz. örneğin burun çok iyidir. Çalıştırıldığında, geçerli ağaçta testler bulur ve çalıştırır.

Güncellenmiş:

Burun öncesi günlerimden bazı kodlar. Muhtemelen modül adlarının açık listesini istemezsiniz, ancak geri kalanı sizin için yararlı olacaktır.

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
Bu yaklaşımın avantajı, tüm test modüllerinizi açıkça bir test_all.py modülüne içe aktarma ve isteğe bağlı olarak bazı modüllerdeki test paketini ilan edebileceğiniz ve diğerlerinde değil test birimini.main () olarak çağırmanız mıdır?
Corey Porter

1
Burnu denedim ve mükemmel çalışıyor. Projemde kurulumu ve çalıştırması kolaydı. Hatta bir sanal satır içinde çalışan birkaç satır komut dosyasıyla otomatikleştirebildim. Burun için +1!
Jesse Webb

Her zaman gerçekleştirilemez: bazen projenin içe aktarılan yapısı, modüllerde ithalatı yapmaya çalışırsa burnun karışmasına neden olabilir.
chiffa

4
O Not burun "son birkaç yıldır bakım modunda" olmuştur ve şu anda kullanımı tavsiye edilir nose2 , pytest , ya da sade unittest / unittest2 yeni projeler için.
Kurt Peek

hiç bir test örneği nesnesinden testleri çalıştırmayı denediniz mi?
Pinokyo

96

Python 3'te kullanıyorsanız unittest.TestCase:

  • Dizininizde boş (veya başka bir şekilde) bir __init__.pydosyanız olmalıdırtest ( adlandırılmalıdır )test/
  • İçindeki test dosyalarınız test/kalıpla eşleşir test_*.py. Altındaki bir alt dizinin içinde olabilirler test/ve bu alt dizinler herhangi bir şey olarak adlandırılabilir.

Ardından, tüm testleri aşağıdakilerle çalıştırabilirsiniz:

python -m unittest

Bitti! 100 çizgiden az bir çözüm. Umarım başka bir python acemi bunu bularak zaman kazandırır.


3
Varsayılan olarak yalnızca "test" ile başlayan dosya adlarındaki testleri arar
Shawabawa

3
Bu doğru, asıl soru "Her birim test modülünün _ *. Py. Test formunda olduğu" gerçeğine işaret etti, bu yüzden bu cevap doğrudan cevapta. Şimdi cevabı daha açık olacak şekilde güncelledim
tmck-code

1
Teşekkürler, Travis Bear'ın cevabını kullanmamda eksik olan şey.
Jeremy Cochoy

65

Bu artık doğrudan unittest: unittest.TestLoader.discover adresinden mümkün .

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

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

3
Ben de bu yöntemi denedim, çift testleri var, ama mükemmel çalışıyor. Mükemmel!!! Ama merak ediyorum sadece 4 testim var. Birlikte 0.032'ler çalıştırıyorlar, ama hepsini çalıştırmak için bu yöntemi kullandığımda sonuç elde ediyorum .... ---------------------------------------------------------------------- Ran 4 tests in 0.000s OKNeden? Fark, nereden geliyor?
simkus

Komut satırından böyle görünen bir dosyayı çalıştırırken sorun yaşıyorum. Nasıl çağrılmalı?
Dustin Michels

python file.py
slaughter98

1
Kusursuz çalıştı! Sadece test / dizininize ayarlayın ve sonra start_id = "./" ayarlayın. IMHO, bu cevap şimdi (Python 3.7) kabul edilen yol!
jjwdesign

Son satırı ´res = runner.run (suite) olarak değiştirebilirsiniz; sys.exit (res.wasSuccessful () else 1 ise) ´ Doğru bir çıkış kodu istiyorsanız
Sadap

32

Biraz yukarıdaki kodu inceleyerek (özellikle TextTestRunnerve kullanarak defaultTestLoader), oldukça yakın olabildim. Sonunda benim kodlarımı diğer tüm sorunlarımı düzeltti "elle" eklemek yerine, sadece tek bir suit kurucu tüm test suit geçirerek sabit. İşte benim çözümüm.

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)

Evet, muhtemelen burnu kullanmak, bunu yapmaktan daha kolaydır, ancak bu konu dışındadır.


iyi, geçerli dizin için iyi çalışıyor, nasıl doğrudan alt çağırmak için?
Larry Cai

Larry, özyinelemeli test keşfi için yeni cevaba ( stackoverflow.com/a/24562019/104143 ) bakın
Peter Kofler

hiç bir test örneği nesnesinden testleri çalıştırmayı denediniz mi?
Pinokyo

25

Tüm testleri çeşitli test senaryosu sınıflarından çalıştırmak istiyorsanız ve bunları açıkça belirtmekten mutluluk duyarsanız, bunu şu şekilde yapabilirsiniz:

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)

uclidbenim projem nerede TestSymbolsve TestPatternsalt sınıfları TestCase.


Gönderen unittest.TestLoader docs : "Normalde, bu sınıfın bir örneğini oluşturmak için gerek yoktur; unittest modül unittest.defaultTestLoader olarak paylaşılabilir bir örneğini sunar." Ayrıca TestSuitebir yinelemeyi bir argüman olarak kabul ettiğinden , yinelenmekten kaçınmak için söz konusu yinelemeyi bir döngüde oluşturabilirsiniz loader.loadTestsFromTestCase.
İki Bit Simyacı

@ İki Bit Simyacı özellikle ikinci noktanız güzel. Eklemek için kodu değiştirirdim ama test edemiyorum. (İlk mod, beğenmem için Java gibi çok fazla görünmesini sağlar .. gerçi mantıksız olduğumu fark ettim (onlara bir deve durumda değişken adları vidalayın).
demented hedgehog

Bu benim favorim, çok temiz. Bunu paketleyip normal komut satırımda bir argüman haline getirebildim.
MarkII

15

Ben discoveryöntem ve bir aşırı load_tests(kod, bence) sayı satır satır sonuç elde etmek için aşırı yükleme kullandık :

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

Beşler üzerinde infaz gibi bir şey

Ran 27 tests in 0.187s
OK

Bu sadece python2.7 için kullanılabilir, sanırım
Larry Cai

@larrycai Belki, genellikle Python 3, bazen Python 2.7 kullanıyorum. Soru belirli bir versiyona bağlı değildi.
rds

Ben Python 3.4 üzerindeyim ve keşfederek döngüyü gereksiz kılan bir paketi döndürür.
Dunes

Gelecek için Larry's: "Test keşfi de dahil olmak üzere Python 2.7'deki unittest'e birçok yeni özellik eklendi. Unittest2 , bu özellikleri Python'un önceki sürümleriyle kullanmanızı sağlar."
İki Bit Simyacı

8

Çeşitli yaklaşımlar denedim ama hepsi kusurlu görünüyor ya da bazı kodlar yapmak zorundayım, bu sinir bozucu. Ancak linux altında rahat bir yol var, bu da her testi belirli bir düzende bulmak ve onları tek tek çağırmak.

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

ve en önemlisi, kesinlikle işe yarıyor.


7

Paketlenmiş bir kütüphane veya uygulama olması durumunda , bunu yapmak istemezsiniz. setuptools sizin için yapacak .

Bu komutu kullanmak için, projenizin sınamaları unittestbir işlev, TestCase sınıfı veya yöntemi veya TestCasesınıfları içeren bir modül veya paket tarafından bir sınama paketine sarılmalıdır . Adlandırılmış paket bir modülse ve modülün bir additional_tests()işlevi varsa, çağrılır ve sonuç (a unittest.TestSuite) olması gereken çalıştırılacak testlere eklenir. Adlandırılmış paket bir paketse , tüm alt modüller ve alt paketler tüm test paketine özyineli olarak eklenir .

Sadece kök test paketinizin nerede olduğunu söyle, örneğin:

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

Ve koş python setup.py test.

Dosya discoveriçe aktarma özelliğini kullandığından , test paketinizde göreli içe aktarma işlemlerinden kaçınmazsanız, dosya tabanlı bulma Python 3'te sorunlu olabilir . İsteğe bağlı desteklese de top_level_dir, bazı sonsuz yineleme hataları yaşadım. Dolayısıyla, paketlenmemiş bir kod için basit bir çözüm aşağıdakileri __init__.pytest paketinize koymaktır (bkz. Load_tests Protokolü ).

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

Güzel cevap ve konuşlandırmadan önce testi otomatikleştirmek için kullanılabilir! Teşekkürler
Arthur Clerc-Gherardi

4

PyDev / LiClipse kullanıyorum ve GUI'den tüm testleri bir kerede nasıl çalıştıracağımı gerçekten anlayamadım. (değiştir: kök test klasörünü sağ tıklayıpRun as -> Python unit-test

Bu benim geçici çözüm:

import unittest

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

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

Bu kodu alltest dizinimdeki bir modüle koydum . Bu modülü LiClipse'den bir unittest olarak çalıştırırsam tüm testler yapılır. Yalnızca belirli veya başarısız testleri tekrarlamak istersem yalnızca bu testler çalıştırılır. Komut satırı test koşucumu da etkilemez (nosetests) - yok sayılır.

Argümanları discoverproje kurulumunuza göre değiştirmeniz gerekebilir .


Tüm test dosyalarının adları ve test yöntemleri "test_" ile başlamalıdır. Aksi takdirde "Farklı çalıştır -> Python birim testi" komutu bunları bulamaz.
Stefan

2

Stephen Cagle'ın cevabına dayanarak iç içe test modülleri için destek ekledim.

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)

Kod tüm alt dizinleri arar .için *Tests.pydaha sonra yüklenen dosyalar. Her birinin , sırayla yüklenen ve birbiri ardına yürütülen *Tests.pytek bir sınıf içermesini bekler *Tests(unittest.TestCase).

Bu, dizinlerin / modüllerin keyfi derin iç içe yerleştirilmesiyle çalışır, ancak aradaki her dizinin __init__.pyen azından boş bir dosya içermesi gerekir . Bu, testin eğik çizgileri (veya ters eğik çizgileri) noktalarla değiştirerek iç içe modülleri yüklemesine izin verir (bkz. replace_slash_by_dot).


2

Bu eski bir soru, ama şimdi (2019'da) benim için işe yarayan şey:

python -m unittest *_test.py

Tüm test dosyalarım kaynak dosyalar ile aynı klasörde ve ile bitiyor _test.



1

Bu BASH betiği, hangi çalışma dizininde olursanız olun, dosya sistemindeki HER YERDEN python unittest test dizinini yürütür: çalışma dizini her zaman bu testdizinin bulunduğu yerde olmalıdır .

TÜM TESTLER, bağımsız $ PWD

unittest Python modülü, nerede ( discover -sseçenek kullanılarak ) söylemediğiniz sürece geçerli dizininize duyarlıdır .

Bu, ./srcveya ./exampleçalışma dizininde kalırken yararlıdır ve hızlı bir genel birim testine ihtiyacınız vardır:

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

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

SEÇİLMİŞ TESTLER, bağımsız $ PWD

Bu yardımcı program adı: runone.pyve şöyle kullanın:

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)

test/__init__.pyÜretim sırasında paketinizi / bellek yükünüzü yükleyecek bir dosyaya gerek yoktur .


-3

Komut satırından testleri çalıştırmak için bir sarıcı oluşturarak yaklaşımım :

#!/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())

Kolaylık olması açısından, lütfen PEP8 olmayan kodlama standartlarımı özür dilerim .

Ardından, tüm testleriniz için ortak bileşenler için BaseTest sınıfı oluşturabilirsiniz, böylece testinizin her biri şöyle görünecektir:

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

Çalıştırmak için, komut satırı bağımsız değişkenlerinin bir parçası olarak testleri belirtmeniz yeterlidir, örneğin:

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

2
bu cevabın çoğunun test keşfi (loglama vb.) ile ilgisi yoktur. Yığın Taşması, ilgisiz kodu göstermeyen soruları yanıtlamak içindir.
Corey Goldberg
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.