Bagaimana cara menjalankan semua tes unit Python di direktori?

315

Saya memiliki direktori yang berisi tes unit Python saya. Setiap modul tes unit berbentuk tes _ *. Py . Saya mencoba membuat file bernama all_test.py yang akan, Anda dapat menebaknya, menjalankan semua file dalam formulir tes yang disebutkan di atas dan mengembalikan hasilnya. Saya telah mencoba dua metode sejauh ini; keduanya gagal. Saya akan menunjukkan dua metode, dan saya berharap seseorang di luar sana tahu bagaimana sebenarnya melakukan ini dengan benar.

Untuk upaya pertama saya yang berani, saya berpikir "Jika saya hanya mengimpor semua modul pengujian saya di file, dan kemudian memanggil barang ini unittest.main(), itu akan berhasil, kan?" Ya, ternyata saya salah.

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

Ini tidak berhasil, hasil yang saya dapatkan adalah:

$ python all_test.py 

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

OK

Untuk percobaan kedua saya, saya kira, oke, mungkin saya akan mencoba melakukan pengujian keseluruhan ini dengan cara yang lebih "manual". Jadi saya mencoba melakukan itu di bawah ini:

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

Ini juga tidak berhasil, tetapi tampaknya sangat dekat!

$ 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

Saya sepertinya memiliki beberapa suite, dan saya dapat menjalankan hasilnya. Saya sedikit khawatir tentang fakta bahwa ia mengatakan hanya saya run=1, sepertinya memang seharusnya begitu run=2, tetapi ini adalah kemajuan. Tetapi bagaimana saya meneruskan dan menampilkan hasilnya ke main? Atau bagaimana saya membuatnya pada dasarnya bekerja sehingga saya bisa menjalankan file ini, dan dengan demikian, jalankan semua tes unit dalam direktori ini?

Stephen Cagle
sumber
1
Lewati ke bawah ke jawaban Travis jika Anda menggunakan Python 2.7+
Rocky
apakah Anda pernah mencoba menjalankan tes dari objek contoh uji?
Pinocchio
Lihat jawaban ini untuk solusi dengan struktur file contoh.
Derek Soike

Jawaban:

477

Dengan Python 2.7 dan lebih tinggi Anda tidak perlu menulis kode baru atau menggunakan alat pihak ketiga untuk melakukan ini; eksekusi tes rekursif melalui baris perintah built-in. Masukkan ke __init__.pydalam direktori pengujian Anda dan:

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

Anda dapat membaca lebih lanjut di dokumentasi python 2.7 atau python 3.x unittest.

Travis Bear
sumber
11
masalah meliputi: ImportError: Direktori mulai tidak dapat diimpor:
zinking
6
Setidaknya dengan Python 2.7.8 di Linux tidak ada permintaan baris perintah yang memberi saya rekursi. Proyek saya memiliki beberapa sub proyek yang unit testnya tinggal di masing-masing direktori "unit_tests / <subproject> / python /". Jika saya menentukan jalur seperti itu, tes unit untuk sub proyek tersebut dijalankan, tetapi dengan hanya "unit_tests" sebagai argumen direktori pengujian, tidak ada tes yang ditemukan (bukan semua tes untuk semua sub proyek, seperti yang saya harapkan). Ada petunjuk?
user686249
6
Tentang rekursi: Perintah pertama tanpa <test_directory> default ke "." dan berulang ke submodula . Yaitu, semua direktori tes yang ingin Anda temukan harus memiliki init .py. Jika mereka melakukannya, mereka akan ditemukan oleh perintah temukan. Baru saja mencobanya, itu berhasil.
Emil Stenström
Ini berhasil untuk saya. Saya memiliki folder tes dengan empat file, jalankan ini dari terminal Linux saya, hal-hal hebat.
JasTonAChair
5
Terima kasih! Mengapa ini bukan jawaban yang diterima? Dalam pandangan saya, jawaban yang lebih baik adalah selalu yang tidak memerlukan dependensi eksternal ...
Jonathan Benn
108

Anda dapat menggunakan pelari ujian yang akan melakukan ini untuk Anda. Hidung sangat bagus misalnya. Saat dijalankan, ia akan menemukan tes di pohon saat ini dan menjalankannya.

Diperbarui:

Ini beberapa kode dari masa pra-hidung saya. Anda mungkin tidak ingin daftar nama modul yang eksplisit, tetapi mungkin sisanya akan berguna bagi Anda.

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)
Ned Batchelder
sumber
2
Apakah keuntungan dari pendekatan ini dibandingkan hanya mengimpor semua modul pengujian Anda secara eksplisit ke satu modul test_all.py dan memanggil unittest.main () yang secara opsional Anda dapat mendeklarasikan suite uji dalam beberapa modul dan tidak pada modul lain?
Corey Porter
1
Saya mencoba hidung dan itu bekerja dengan sempurna. Mudah untuk menginstal dan menjalankan proyek saya. Saya bahkan dapat mengotomatiskannya dengan beberapa baris skrip, berjalan di dalam virtualenv. +1 untuk hidung!
Jesse Webb
Tidak selalu bisa dilakukan: kadang-kadang mengimpor struktur proyek dapat menyebabkan hidung semakin bingung jika mencoba menjalankan impor pada modul.
chiffa
4
Perhatikan bahwa hidung telah "dalam mode pemeliharaan selama beberapa tahun terakhir" dan saat ini disarankan untuk menggunakan nose2 , pytest , atau sekadar unittest / unittest2 untuk proyek-proyek baru.
Kurt Peek
apakah Anda pernah mencoba menjalankan tes dari objek contoh uji?
Pinocchio
96

Di python 3, jika Anda menggunakan unittest.TestCase:

  • Anda harus memiliki __init__.pyfile kosong (atau sebaliknya) di testdirektori Anda ( harus dinamaitest/ )
  • File tes Anda di dalam test/cocok dengan pola test_*.py. Mereka dapat berada di dalam subdirektori di bawah test/, dan subdirektori tersebut dapat dinamai apa saja.

Kemudian, Anda dapat menjalankan semua tes dengan:

python -m unittest

Selesai! Sebuah solusi kurang dari 100 baris. Semoga pemula python lain menghemat waktu dengan menemukan ini.

tmck-code
sumber
3
Perhatikan bahwa secara default hanya mencari tes dalam nama file yang dimulai dengan "test"
Shawabawa
3
Itu benar, pertanyaan awal merujuk pada fakta bahwa "Setiap modul tes unit adalah tes bentuk _ *. Py.", Jadi jawaban ini dalam balasan langsung. Sekarang saya telah memperbarui jawabannya menjadi lebih eksplisit
tmck-code
1
Terima kasih, apa yang hilang bagi saya untuk menggunakan jawaban Travis Bear.
Jeremy Cochoy
65

Ini sekarang mungkin langsung dari 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)
pembantaian98
sumber
3
Saya telah mencoba metode ini juga, melakukan beberapa tes, tetapi berfungsi dengan baik. Luar Biasa !!! Tapi saya ingin tahu saya hanya punya 4 tes. Bersama-sama mereka menjalankan 0,032, tetapi ketika saya menggunakan metode ini untuk menjalankan semuanya, saya mendapatkan hasil .... ---------------------------------------------------------------------- Ran 4 tests in 0.000s OKMengapa? Bedanya, dari mana asalnya?
simkus
Saya mengalami kesulitan menjalankan file yang terlihat seperti ini dari baris perintah. Bagaimana cara memohonnya?
Dustin Michels
python file.py
slaughter98
1
Bekerja dengan sempurna! Cukup atur di test / dir Anda dan kemudian atur start_id = "./". IMHO, jawaban ini sekarang (Python 3.7) adalah cara yang diterima!
jjwdesign
Anda dapat mengubah baris terakhir ke ´res = runner.run (suite); sys.exit (0 if res.wasSuccessful () else 1) ´ jika Anda ingin kode keluar yang benar
Sadap
32

Nah dengan mempelajari kode di atas sedikit (khusus menggunakan TextTestRunnerdan defaultTestLoader), saya bisa mendapatkan cukup dekat. Akhirnya saya memperbaiki kode saya dengan hanya meneruskan semua test suite ke konstruktor suite tunggal, daripada menambahkannya "secara manual", yang memperbaiki masalah saya yang lain. Jadi, inilah solusi saya.

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)

Ya, mungkin lebih mudah menggunakan hidung daripada melakukan ini, tapi itu intinya.

Stephen Cagle
sumber
bagus, ini berfungsi dengan baik untuk direktori saat ini, bagaimana menjalankan sub-langsung?
Larry Cai
Larry, lihat jawaban baru ( stackoverflow.com/a/24562019/104143 ) untuk penemuan tes rekursif
Peter Kofler
apakah Anda pernah mencoba menjalankan tes dari objek contoh uji?
Pinocchio
25

Jika Anda ingin menjalankan semua tes dari berbagai kelas kasus tes dan Anda senang menentukannya secara eksplisit maka Anda dapat melakukannya seperti ini:

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)

di mana uclidproyek saya dan TestSymbolsdan TestPatternsmerupakan subclass dari TestCase.

landak gila
sumber
Dari dokumen unittest.TestLoader : "Biasanya, tidak perlu membuat instance dari kelas ini; modul unittest menyediakan instance yang dapat dibagikan sebagai unittest.defaultTestLoader." Juga karena TestSuitemenerima iterable sebagai argumen, Anda bisa membuat kata iterable dalam satu loop untuk menghindari pengulangan loader.loadTestsFromTestCase.
Alchemist Dua-Bit
@ Alchemist Dua-Bit, poin kedua Anda khususnya bagus. Saya akan mengubah kode untuk dimasukkan tetapi saya tidak bisa mengujinya. (Mod pertama akan membuatnya terlihat seperti Java untuk saya sukai .. meskipun saya menyadari bahwa saya tidak rasional (mengacaukan mereka dan nama variabel case unta mereka)).
landak gila
Ini favorit saya, sangat bersih. Mampu mengemas ini dan menjadikannya argumen di baris perintah biasa saya.
MarkII
15

Saya telah menggunakan discovermetode dan kelebihan load_testsuntuk mencapai hasil ini dalam baris kode (minimal, menurut saya):

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

Eksekusi pada balita kira-kira seperti itu

Ran 27 tests in 0.187s
OK
rds
sumber
ini hanya tersedia untuk python2.7, saya kira
Larry Cai
@larrycai Mungkin, saya biasanya menggunakan Python 3, terkadang Python 2.7. Pertanyaannya tidak terkait dengan versi tertentu.
rds
Saya menggunakan Python 3.4 dan menemukan mengembalikan suite, membuat loop berlebihan.
Bukit pasir
Untuk masa depan Larry: "Banyak fitur baru ditambahkan ke unittest di Python 2.7, termasuk test discovery. Unittest2 memungkinkan Anda untuk menggunakan fitur-fitur ini dengan versi Python yang lebih lama."
Alchemist Dua-Bit
8

Saya mencoba berbagai pendekatan tetapi semua tampaknya cacat atau saya harus membuat beberapa kode, itu menjengkelkan. Tapi ada cara yang meyakinkan di linux, yaitu hanya dengan menemukan setiap tes melalui pola tertentu dan kemudian memohonnya satu per satu.

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

dan yang paling penting, itu pasti berhasil.

zinking
sumber
7

Dalam hal pustaka atau aplikasi terpaket , Anda tidak ingin melakukannya. setuptools akan melakukannya untukmu .

Untuk menggunakan perintah ini, tes proyek Anda harus dibungkus dalam unittesttest suite dengan fungsi, kelas atau metode TestCase, atau modul atau paket yang berisi TestCasekelas. Jika suite yang dinamai adalah modul, dan modul memiliki additional_tests()fungsi, ia dipanggil dan hasilnya (yang harus a unittest.TestSuite) ditambahkan ke tes yang akan dijalankan. Jika suite yang diberi nama adalah paket, setiap submodul dan subpackages ditambahkan secara rekursif ke keseluruhan test suite .

Katakan saja di mana paket pengujian root Anda, seperti:

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

Dan lari python setup.py test.

Penemuan berbasis file mungkin bermasalah di Python 3, kecuali Anda menghindari impor relatif di suite pengujian Anda, karena discovermenggunakan impor file. Meskipun mendukung opsional top_level_dir, tetapi saya memiliki beberapa kesalahan rekursi tak terbatas. Jadi solusi sederhana untuk kode yang tidak dikemas adalah dengan memasukkan yang berikut dalam __init__.pypaket pengujian Anda (lihat load_tests Protocol ).

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
saaj
sumber
Jawaban yang bagus, dan dapat digunakan untuk mengotomatiskan pengujian sebelum digunakan! Terima kasih
Arthur Clerc-Gherardi
4

Saya menggunakan PyDev / LiClipse dan belum benar-benar tahu cara menjalankan semua tes sekaligus dari GUI. (edit: Anda klik kanan folder tes root dan pilihRun as -> Python unit-test

Ini adalah solusi saya saat ini:

import unittest

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

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

Saya memasukkan kode ini dalam modul yang disebut all di direktori pengujian saya. Jika saya menjalankan modul ini sebagai yang terbaik dari LiClipse maka semua tes dijalankan. Jika saya meminta hanya mengulang tes tertentu atau gagal maka hanya tes yang dijalankan. Itu tidak mengganggu pelari uji commandline saya (nosetests) - diabaikan.

Anda mungkin perlu mengubah argumen discoverberdasarkan pengaturan proyek Anda.

Bukit pasir
sumber
Nama semua file uji dan metode pengujian harus dimulai dengan "test_". Kalau tidak, perintah "Run as -> Python unit test" tidak akan menemukannya.
Stefan
2

Berdasarkan jawaban Stephen Cagle I menambahkan dukungan untuk modul tes bersarang.

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)

Kode mencari semua subdirektori .untuk *Tests.pyfile yang kemudian dimuat. Diharapkan masing *Tests.py- masing berisi kelas tunggal *Tests(unittest.TestCase)yang dimuat pada gilirannya dan dieksekusi satu demi satu.

Ini berfungsi dengan bersarang dalam direktori / modul yang bersarang, tetapi setiap direktori di antaranya perlu mengandung __init__.pyfile kosong setidaknya. Ini memungkinkan pengujian memuat modul bersarang dengan mengganti garis miring (atau garis miring terbalik) dengan titik (lihat replace_slash_by_dot).

Peter Kofler
sumber
2

Ini adalah pertanyaan lama, tetapi apa yang berhasil bagi saya sekarang (pada tahun 2019) adalah:

python -m unittest *_test.py

Semua file pengujian saya berada di folder yang sama dengan file sumber dan berakhir dengan _test.

Plasty Grove
sumber
1

Skrip BASH ini akan mengeksekusi direktori uji python unittest dari MANA SAJA dalam sistem file, tidak peduli apa direktori tempat Anda berada: direktori kerjanya selalu berada di tempat testdirektori itu berada.

SEMUA UJI, $ PWD independen

modul Python unittest sensitif terhadap direktori Anda saat ini, kecuali Anda mengatakannya di mana (menggunakan discover -sopsi).

Ini berguna ketika tetap berada di direktori kerja ./srcatau ./exampledan Anda membutuhkan tes unit keseluruhan cepat:

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

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

UJI TERPILIH, independen $ PWD

Saya beri nama file utilitas ini: runone.py dan menggunakannya seperti ini:

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)

Tidak perlu untuk test/__init__.py file membebani paket Anda / memori-overhead selama produksi.

John Greene
sumber
-3

Inilah pendekatan saya dengan membuat pembungkus untuk menjalankan tes dari baris perintah:

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

Demi kesederhanaan, permisi non- PEP8 saya standar pengkodean .

Kemudian Anda bisa membuat kelas BaseTest untuk komponen umum untuk semua tes Anda, sehingga setiap tes Anda akan terlihat seperti:

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

Untuk menjalankan, Anda cukup menentukan tes sebagai bagian dari argumen baris perintah, misalnya:

./run_tests.py -h http://example.com/ tests/**/*.py
kenorb
sumber
2
sebagian besar jawaban ini tidak ada hubungannya dengan penemuan tes (yaitu penebangan, dll). Stack Overflow adalah untuk menjawab pertanyaan, tidak memamerkan kode yang tidak terkait.
Corey Goldberg