Bagaimana Anda menghasilkan tes unit dinamis (parameter) dalam python?


234

Saya memiliki beberapa jenis data pengujian dan ingin membuat unit test untuk setiap item. Ide pertama saya adalah melakukannya seperti ini:

import unittest

l = [["foo", "a", "a",], ["bar", "a", "b"], ["lee", "b", "b"]]

class TestSequence(unittest.TestCase):
    def testsample(self):
        for name, a,b in l:
            print "test", name
            self.assertEqual(a,b)

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

Kelemahan dari ini adalah bahwa ia menangani semua data dalam satu tes. Saya ingin membuat satu tes untuk setiap item dengan cepat. Ada saran?



2
Tautan yang baik yang dapat memberikan jawaban: eli.thegreenplace.net/2014/04/02/…
gaborous

Jawaban:


173

Ini disebut "parametrization".

Ada beberapa alat yang mendukung pendekatan ini. Misalnya:

Kode yang dihasilkan terlihat seperti ini:

from parameterized import parameterized

class TestSequence(unittest.TestCase):
    @parameterized.expand([
        ["foo", "a", "a",],
        ["bar", "a", "b"],
        ["lee", "b", "b"],
    ])
    def test_sequence(self, name, a, b):
        self.assertEqual(a,b)

Yang akan menghasilkan tes:

test_sequence_0_foo (__main__.TestSequence) ... ok
test_sequence_1_bar (__main__.TestSequence) ... FAIL
test_sequence_2_lee (__main__.TestSequence) ... ok

======================================================================
FAIL: test_sequence_1_bar (__main__.TestSequence)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/usr/local/lib/python2.7/site-packages/parameterized/parameterized.py", line 233, in <lambda>
    standalone_func = lambda *a: func(*(a + p.args), **p.kwargs)
  File "x.py", line 12, in test_sequence
    self.assertEqual(a,b)
AssertionError: 'a' != 'b'

Untuk alasan historis, saya akan meninggalkan jawaban asli sekitar 2008):

Saya menggunakan sesuatu seperti ini:

import unittest

l = [["foo", "a", "a",], ["bar", "a", "b"], ["lee", "b", "b"]]

class TestSequense(unittest.TestCase):
    pass

def test_generator(a, b):
    def test(self):
        self.assertEqual(a,b)
    return test

if __name__ == '__main__':
    for t in l:
        test_name = 'test_%s' % t[0]
        test = test_generator(t[1], t[2])
        setattr(TestSequense, test_name, test)
    unittest.main()

24
Sebenarnya, bignose, kode ini TIDAK menghasilkan nama yang berbeda untuk setiap tes (sebenarnya tidak akan berhasil jika tidak). Dalam contoh yang diberikan, tes yang dijalankan akan diberi nama "test_foo", "test_bar", dan "test_lee". Jadi manfaat yang Anda sebutkan (dan itu besar) dipertahankan selama Anda menghasilkan nama yang masuk akal.
Toji

1
Seperti jawaban yang diberikan oleh @codeape menyatakan, hidung menangani ini. Namun, hidung tampaknya tidak menangani Unicode; oleh karena itu bagi saya ini adalah solusi yang lebih disukai. +1
Keith Pinson

5
Jadi perhatikan, bahwa jawaban yang lebih tepat diberikan dalam pertanyaan duplikat : stackoverflow.com/a/2799009/322020 - Anda harus menggunakan .__name__ =untuk mengaktifkan .exact_methodpengujian
Nakilon

7
Mengapa kode yang memodifikasi kelas muncul di if __name__ == '__main__'conditional? Tentunya itu harus keluar dari ini untuk berjalan pada waktu impor (mengingat bahwa modul python hanya diimpor sekali bahkan jika diimpor dari beberapa tempat yang berbeda)
SpoonMeiser

4
Saya rasa ini bukan solusi yang baik. Kode unittest tidak harus bergantung pada cara panggilan itu. TestCase harus dapat digunakan di hidung atau pytest atau lingkungan pengujian yang berbeda.
guettli

146

Menggunakan unittest (sejak 3.4)

Sejak Python 3.4, unittestpaket perpustakaan standar memiliki subTestmanajer konteks.

Lihat dokumentasi:

Contoh:

from unittest import TestCase

param_list = [('a', 'a'), ('a', 'b'), ('b', 'b')]

class TestDemonstrateSubtest(TestCase):
    def test_works_as_expected(self):
        for p1, p2 in param_list:
            with self.subTest():
                self.assertEqual(p1, p2)

Anda juga dapat menentukan pesan khusus dan nilai parameter untuk subTest():

with self.subTest(msg="Checking if p1 equals p2", p1=p1, p2=p2):

Menggunakan hidung

The hidung kerangka pengujian mendukung ini .

Contoh (kode di bawah ini adalah seluruh isi file yang berisi tes):

param_list = [('a', 'a'), ('a', 'b'), ('b', 'b')]

def test_generator():
    for params in param_list:
        yield check_em, params[0], params[1]

def check_em(a, b):
    assert a == b

Output dari perintah nosetests:

> nosetests -v
testgen.test_generator('a', 'a') ... ok
testgen.test_generator('a', 'b') ... FAIL
testgen.test_generator('b', 'b') ... ok

======================================================================
FAIL: testgen.test_generator('a', 'b')
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/usr/lib/python2.5/site-packages/nose-0.10.1-py2.5.egg/nose/case.py", line 203, in runTest
    self.test(*self.arg)
  File "testgen.py", line 7, in check_em
    assert a == b
AssertionError

----------------------------------------------------------------------
Ran 3 tests in 0.006s

FAILED (failures=1)

3
Itu cara yang sangat bersih untuk menghasilkan kasus uji secara dinamis.
Gaborous

Namun perlu diperhatikan, 'setup ()' tidak akan tahu variabel apa yang digunakan sebagai argumen untuk menghasilkan. Sebenarnya setup () tidak akan tahu tes apa yang sedang berjalan, atau vars yang diatur di dalam test_generator (). Ini menyulitkan pemeriksaan kewarasan dalam setup (), dan itu salah satu alasan bahwa beberapa orang lebih suka py.test.
Scott Prive

1
Terpilih untuk bagian pembaruan. Apa yang saya butuhkan. :)
Saurabh Shrivastava

1
Apakah ada cara untuk menjalankan versi unittest dengan pytest, sehingga akan menjalankan semua case dan tidak berhenti pada parameter gagal pertama?
kakk11

1
Seperti yang disebutkan oleh @ kakk11, jawaban ini (dan subTest secara umum) tidak bekerja dengan pytest. Ini adalah masalah yang diketahui. Ada plugin yang dikembangkan secara aktif untuk membuatnya berfungsi: github.com/pytest-dev/pytest-subtests
Jérémie

76

Ini bisa diselesaikan dengan elegan menggunakan Metaclasses:

import unittest

l = [["foo", "a", "a",], ["bar", "a", "b"], ["lee", "b", "b"]]

class TestSequenceMeta(type):
    def __new__(mcs, name, bases, dict):

        def gen_test(a, b):
            def test(self):
                self.assertEqual(a, b)
            return test

        for tname, a, b in l:
            test_name = "test_%s" % tname
            dict[test_name] = gen_test(a,b)
        return type.__new__(mcs, name, bases, dict)

class TestSequence(unittest.TestCase):
    __metaclass__ = TestSequenceMeta

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

1
Ini berhasil bagi saya dengan Selenium. Sebagai catatan, di kelas TestSequence, Anda dapat menentukan metode "statis" seperti setUp (self), is_element_present (self, how, what), ... tearDown (self). Menempatkannya SETELAH pernyataan " metaclass = TestSequenceMeta" bekerja.
Cinta dan kedamaian - Joe Codeswell

5
Solusi ini lebih baik daripada yang dipilih sebagai IMHO yang diterima.
petroslamb

2
@petroslamb __new__Metode dalam metaclass dipanggil saat kelas itu sendiri didefinisikan, bukan ketika instance pertama dibuat. Saya akan membayangkan metode ini menciptakan metode tes secara dinamis lebih kompatibel dengan introspeksi yang digunakan oleh unittestuntuk menentukan berapa banyak tes di kelas (yaitu mungkin menyusun daftar tes sebelum pernah membuat turunan dari kelas itu).
BillyBBone

11
Catatan: dalam python 3, ubah ini menjadi:class TestSequence(unittest.TestCase, metaclass=TestSequenceMeta):[...]
Mathieu_Du

3
Bisakah Anda menggunakan dctbukan dict? Menggunakan kata kunci sebagai nama variabel membingungkan dan rentan kesalahan.
npfoss

49

Pada Python 3.4 subtitle telah diperkenalkan ke unittest untuk tujuan ini. Lihat dokumentasi untuk detailnya. TestCase.subTest adalah manajer konteks yang memungkinkan seseorang untuk mengisolasi pernyataan dalam suatu pengujian sehingga kegagalan akan dilaporkan dengan informasi parameter tetapi tidak menghentikan pelaksanaan pengujian. Inilah contoh dari dokumentasi:

class NumbersTest(unittest.TestCase):

def test_even(self):
    """
    Test that numbers between 0 and 5 are all even.
    """
    for i in range(0, 6):
        with self.subTest(i=i):
            self.assertEqual(i % 2, 0)

Output dari uji coba adalah:

======================================================================
FAIL: test_even (__main__.NumbersTest) (i=1)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "subtests.py", line 32, in test_even
    self.assertEqual(i % 2, 0)
AssertionError: 1 != 0

======================================================================
FAIL: test_even (__main__.NumbersTest) (i=3)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "subtests.py", line 32, in test_even
    self.assertEqual(i % 2, 0)
AssertionError: 1 != 0

======================================================================
FAIL: test_even (__main__.NumbersTest) (i=5)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "subtests.py", line 32, in test_even
    self.assertEqual(i % 2, 0)
AssertionError: 1 != 0

Ini juga merupakan bagian dari unittest2 , sehingga tersedia untuk versi Python sebelumnya.


1
Solusi terbaik jika Anda menggunakan python 3.4 dan lebih tinggi.
Max Malysh

4
Menggunakan unittest2, ini juga tersedia untuk Python 2.7.
Bernhard

11
Satu perbedaan utama antara pendekatan ini dan memiliki tes terpisah adalah bahwa status tes tidak disetel ulang setiap kali. (Yaitu, setUp()dan tearDown()tidak berjalan di antara sub-tes.)
Kevin Christopher Henry

1
@KevinChristopherHenry Ya, tetapi self.setUp()secara teori dapat dipanggil secara manual dari dalam subtest. Adapun tearDown, memanggilnya secara otomatis di akhir mungkin sudah cukup.
Acumenus

Saya pikir ini bisa menjadi kuat ketika digunakan bersama dengan pendekatan metaclass di atas.
Nathan Chappell

36

load_tests adalah mekanisme yang sedikit dikenal diperkenalkan pada 2.7 untuk secara dinamis membuat TestSuite. Dengan itu, Anda dapat dengan mudah membuat tes parametrized.

Sebagai contoh:

import unittest

class GeneralTestCase(unittest.TestCase):
    def __init__(self, methodName, param1=None, param2=None):
        super(GeneralTestCase, self).__init__(methodName)

        self.param1 = param1
        self.param2 = param2

    def runTest(self):
        pass  # Test that depends on param 1 and 2.


def load_tests(loader, tests, pattern):
    test_cases = unittest.TestSuite()
    for p1, p2 in [(1, 2), (3, 4)]:
        test_cases.addTest(GeneralTestCase('runTest', p1, p2))
    return test_cases

Kode itu akan menjalankan semua TestCases di TestSuite yang dikembalikan oleh load_tests. Tidak ada tes lain yang secara otomatis dijalankan oleh mekanisme penemuan.

Atau, Anda juga dapat menggunakan warisan seperti yang ditunjukkan dalam tiket ini: http://bugs.python.org/msg151444


1
Kode di atas gagal: TypeError: __init __ () membutuhkan paling banyak 2 argumen (4 diberikan)
maks

2
Menambahkan null default ke parameter ekstra konstruktor.
Javier

Saya lebih suka kode parameter hidung dalam jawaban @ mojo , tetapi untuk klien saya terlalu berguna untuk menghindari ketergantungan tambahan jadi saya akan menggunakan ini untuk mereka.
bijak

1
Solusi ini adalah favorit saya di halaman ini. Baik Nose , disarankan dalam jawaban teratas saat ini, dan Nose2 fork- nya adalah pemeliharaan saja, dan yang kedua menyarankan pengguna sebagai gantinya mencoba pytest . Benar-benar berantakan - saya akan tetap menggunakan pendekatan asli seperti ini!
Sean

1
bonus: kemampuan untuk mendefinisikan kembali metode shortDescription untuk output yang dikirimkan dalam params
fun_vit

33

Itu bisa dilakukan dengan menggunakan pytest . Cukup tulis filetest_me.py dengan konten:

import pytest

@pytest.mark.parametrize('name, left, right', [['foo', 'a', 'a'],
                                               ['bar', 'a', 'b'],
                                               ['baz', 'b', 'b']])
def test_me(name, left, right):
    assert left == right, name

Dan jalankan tes Anda dengan perintah py.test --tb=short test_me.py . Maka hasilnya akan terlihat seperti:

=========================== test session starts ============================
platform darwin -- Python 2.7.6 -- py-1.4.23 -- pytest-2.6.1
collected 3 items

test_me.py .F.

================================= FAILURES =================================
_____________________________ test_me[bar-a-b] _____________________________
test_me.py:8: in test_me
    assert left == right, name
E   AssertionError: bar
==================== 1 failed, 2 passed in 0.01 seconds ====================

Sederhana saja! Juga pytest memiliki lebih banyak fitur seperti fixtures, mark, assert, dll ...


1
Saya sedang mencari contoh sederhana, lurus ke depan bagaimana parametrize kasus uji dengan py.test. Terima kasih banyak!
timgeb

@timgeb saya senang membantu Anda. Periksa tag py.test , untuk lebih banyak contoh. Saya juga menyarankan untuk menggunakan hamcrest untuk menambahkan gula ke dalam pernyataan Anda dengan mutchers yang dapat dibaca manusia, yang dapat dimodifikasi, dikombinasikan atau dibuat dengan cara Anda sendiri. Ditambah lagi kita memiliki godaan-python , pembuatan laporan yang terlihat baguspy.test
Sergey Voronezhskiy

Terima kasih. Saya baru saja mulai pindah dari unittestke py.test. Saya dulu memiliki TestCasekelas dasar yang mampu secara dinamis membuat anak-anak dengan argumen yang berbeda yang akan mereka simpan sebagai variabel kelas ... yang agak sulit digunakan.
timgeb

1
@timgeb Ya Anda benar. Yang paling fitur pembunuh dari py.testadalah yield_fixtures . Yang dapat melakukan pengaturan , mengembalikan beberapa data yang berguna ke dalam pengujian dan setelah tes berakhir membuat teardown . Perlengkapan juga dapat parametirized .
Sergey Voronezhskiy

12

Gunakan perpustakaan ddt . Ini menambahkan dekorator sederhana untuk metode pengujian:

import unittest
from ddt import ddt, data
from mycode import larger_than_two

@ddt
class FooTestCase(unittest.TestCase):

    @data(3, 4, 12, 23)
    def test_larger_than_two(self, value):
        self.assertTrue(larger_than_two(value))

    @data(1, -3, 2, 0)
    def test_not_larger_than_two(self, value):
        self.assertFalse(larger_than_two(value))

Pustaka ini dapat diinstal dengan pip. Itu tidak memerlukan nose, dan bekerja sangat baik dengan unittestmodul perpustakaan standar .


6

Anda akan mendapat manfaat dari mencoba perpustakaan TestScenarios .

testcenarios menyediakan injeksi dependensi bersih untuk tes gaya python unittest. Ini dapat digunakan untuk pengujian antarmuka (menguji banyak implementasi melalui rangkaian uji tunggal) atau untuk injeksi dependensi klasik (memberikan tes dengan dependensi eksternal ke kode tes itu sendiri, yang memungkinkan pengujian mudah dalam situasi yang berbeda).



4

Anda dapat menggunakan plugin nose-ittr ( pip install nose-ittr).

Sangat mudah untuk diintegrasikan dengan tes yang ada, perubahan minimal (jika ada) diperlukan. Ini juga mendukung plugin multi-pemrosesan hidung .

Bukan berarti Anda juga dapat memiliki setupfungsi penyesuaian per pengujian.

@ittr(number=[1, 2, 3, 4])   
def test_even(self):   
    assert_equal(self.number % 2, 0)

Dimungkinkan juga untuk melewatkan nosetestparameter seperti dengan plugin bawaannya attrib, dengan cara ini Anda hanya dapat menjalankan tes khusus dengan parameter tertentu:

nosetest -a number=2

Saya suka pendekatan ini, terutama tingkat per metode yang didukungnya.
Matt

3

Saya menggunakan metaclasses dan dekorator untuk menghasilkan tes. Anda dapat memeriksa implementasi python_wrap_cases saya . Perpustakaan ini tidak memerlukan kerangka uji apa pun.

Contoh Anda:

import unittest
from python_wrap_cases import wrap_case


@wrap_case
class TestSequence(unittest.TestCase):

    @wrap_case("foo", "a", "a")
    @wrap_case("bar", "a", "b")
    @wrap_case("lee", "b", "b")
    def testsample(self, name, a, b):
        print "test", name
        self.assertEqual(a, b)

Output konsol:

testsample_u'bar'_u'a'_u'b' (tests.example.test_stackoverflow.TestSequence) ... test bar
FAIL
testsample_u'foo'_u'a'_u'a' (tests.example.test_stackoverflow.TestSequence) ... test foo
ok
testsample_u'lee'_u'b'_u'b' (tests.example.test_stackoverflow.TestSequence) ... test lee
ok

Anda juga dapat menggunakan generator . Misalnya kode ini menghasilkan semua kemungkinan kombinasi pengujian dengan argumen a__listdanb__list

import unittest
from python_wrap_cases import wrap_case


@wrap_case
class TestSequence(unittest.TestCase):

    @wrap_case(a__list=["a", "b"], b__list=["a", "b"])
    def testsample(self, a, b):
        self.assertEqual(a, b)

Output konsol:

testsample_a(u'a')_b(u'a') (tests.example.test_stackoverflow.TestSequence) ... ok
testsample_a(u'a')_b(u'b') (tests.example.test_stackoverflow.TestSequence) ... FAIL
testsample_a(u'b')_b(u'a') (tests.example.test_stackoverflow.TestSequence) ... FAIL
testsample_a(u'b')_b(u'b') (tests.example.test_stackoverflow.TestSequence) ... ok

2

Saya menemukan ParamUnittest tempo hari ketika melihat kode sumber ke radon ( contoh penggunaan pada repo github ). Ini harus bekerja dengan kerangka kerja lain yang memperpanjang TestCase (seperti Hidung).

Berikut ini sebuah contoh:

import unittest
import paramunittest


@paramunittest.parametrized(
    ('1', '2'),
    #(4, 3),    <---- uncomment to have a failing test
    ('2', '3'),
    (('4', ), {'b': '5'}),
    ((), {'a': 5, 'b': 6}),
    {'a': 5, 'b': 6},
)
class TestBar(TestCase):
    def setParameters(self, a, b):
        self.a = a
        self.b = b

    def testLess(self):
        self.assertLess(self.a, self.b)

2
import unittest

def generator(test_class, a, b):
    def test(self):
        self.assertEqual(a, b)
    return test

def add_test_methods(test_class):
    #First element of list is variable "a", then variable "b", then name of test case that will be used as suffix.
    test_list = [[2,3, 'one'], [5,5, 'two'], [0,0, 'three']]
    for case in test_list:
        test = generator(test_class, case[0], case[1])
        setattr(test_class, "test_%s" % case[2], test)


class TestAuto(unittest.TestCase):
    def setUp(self):
        print 'Setup'
        pass

    def tearDown(self):
        print 'TearDown'
        pass

_add_test_methods(TestAuto)  # It's better to start with underscore so it is not detected as a test itself

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

HASIL:

>>> 
Setup
FTearDown
Setup
TearDown
.Setup
TearDown
.
======================================================================
FAIL: test_one (__main__.TestAuto)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "D:/inchowar/Desktop/PyTrash/test_auto_3.py", line 5, in test
    self.assertEqual(a, b)
AssertionError: 2 != 3

----------------------------------------------------------------------
Ran 3 tests in 0.019s

FAILED (failures=1)

1
Masalah kecil dengan def add_test_methodsfungsi Anda . Seharusnya def _add_test_methods saya pikir
Raychaser

@ Raychaser ... Anda benar .. Saya memperbaikinya tetapi tidak memperbaruinya di sini .... Terima kasih telah mengetahui hal itu.
Arindam Roychowdhury

1

Cukup gunakan metaclasses, seperti yang terlihat di sini;

class DocTestMeta(type):
    """
    Test functions are generated in metaclass due to the way some
    test loaders work. For example, setupClass() won't get called
    unless there are other existing test methods, and will also
    prevent unit test loader logic being called before the test
    methods have been defined.
    """
    def __init__(self, name, bases, attrs):
        super(DocTestMeta, self).__init__(name, bases, attrs)

    def __new__(cls, name, bases, attrs):
        def func(self):
            """Inner test method goes here"""
            self.assertTrue(1)

        func.__name__ = 'test_sample'
        attrs[func.__name__] = func
        return super(DocTestMeta, cls).__new__(cls, name, bases, attrs)

class ExampleTestCase(TestCase):
    """Our example test case, with no methods defined"""
    __metaclass__ = DocTestMeta

Keluaran:

test_sample (ExampleTestCase) ... OK

1

Anda dapat menggunakan TestSuitedan mengkustomisasi TestCasekelas.

import unittest

class CustomTest(unittest.TestCase):
    def __init__(self, name, a, b):
        super().__init__()
        self.name = name
        self.a = a
        self.b = b

    def runTest(self):
        print("test", self.name)
        self.assertEqual(self.a, self.b)

if __name__ == '__main__':
    suite = unittest.TestSuite()
    suite.addTest(CustomTest("Foo", 1337, 1337))
    suite.addTest(CustomTest("Bar", 0xDEAD, 0xC0DE))
    unittest.TextTestRunner().run(suite)

Sementara TestSuite berfungsi, argumen tidak diturunkan ke __init__fungsi.
jadelord

1

Saya telah menemukan bahwa ini bekerja dengan baik untuk tujuan saya, terutama jika saya perlu membuat tes yang melakukan sedikit perbedaan proses pada kumpulan data.

import unittest

def rename(newName):
    def renamingFunc(func):
        func.__name__ == newName
        return func
    return renamingFunc

class TestGenerator(unittest.TestCase):

    TEST_DATA = {}

    @classmethod
    def generateTests(cls):
        for dataName, dataValue in TestGenerator.TEST_DATA:
            for func in cls.getTests(dataName, dataValue):
                setattr(cls, "test_{:s}_{:s}".format(func.__name__, dataName), func)

    @classmethod
    def getTests(cls):
        raise(NotImplementedError("This must be implemented"))

class TestCluster(TestGenerator):

    TEST_CASES = []

    @staticmethod
    def getTests(dataName, dataValue):

        def makeTest(case):

            @rename("{:s}".format(case["name"]))
            def test(self):
                # Do things with self, case, data
                pass

            return test

        return [makeTest(c) for c in TestCluster.TEST_CASES]

TestCluster.generateTests()

The TestGeneratorkelas dapat digunakan untuk bertelur set yang berbeda dari uji kasus seperti TestCluster.

TestClusterdapat dianggap sebagai implementasi dari TestGeneratorantarmuka.


1

Solusi ini berfungsi dengan unittestdan noseuntuk Python 2 dan Python 3:

#!/usr/bin/env python
import unittest

def make_function(description, a, b):
    def ghost(self):
        self.assertEqual(a, b, description)
    print(description)
    ghost.__name__ = 'test_{0}'.format(description)
    return ghost


class TestsContainer(unittest.TestCase):
    pass

testsmap = {
    'foo': [1, 1],
    'bar': [1, 2],
    'baz': [5, 5]}

def generator():
    for name, params in testsmap.iteritems():
        test_func = make_function(name, params[0], params[1])
        setattr(TestsContainer, 'test_{0}'.format(name), test_func)

generator()

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

Terima kasih @ guillaume-jacquenot untuk versi yang ditingkatkan <3!
pel

0

Saya mengalami masalah dengan gaya pengujian parameter yang sangat khusus. Semua tes Selenium kami dapat berjalan secara lokal, tetapi tes tersebut juga harus dapat dijalankan dari jarak jauh terhadap beberapa platform di SauceLabs. Pada dasarnya, saya ingin mengambil sejumlah besar test case yang sudah ditulis dan parameter mereka dengan perubahan kode sesedikit mungkin. Selain itu, saya harus dapat melewatkan parameter ke dalam metode setUp, sesuatu yang saya belum melihat solusi untuk tempat lain.

Inilah yang saya buat:

import inspect
import types

test_platforms = [
    {'browserName': "internet explorer", 'platform': "Windows 7", 'version': "10.0"},
    {'browserName': "internet explorer", 'platform': "Windows 7", 'version': "11.0"},
    {'browserName': "firefox", 'platform': "Linux", 'version': "43.0"},
]


def sauce_labs():
    def wrapper(cls):
        return test_on_platforms(cls)
    return wrapper


def test_on_platforms(base_class):
    for name, function in inspect.getmembers(base_class, inspect.isfunction):
        if name.startswith('test_'):
            for platform in test_platforms:
                new_name = '_'.join(list([name, ''.join(platform['browserName'].title().split()), platform['version']]))
                new_function = types.FunctionType(function.__code__, function.__globals__, new_name,
                                                  function.__defaults__, function.__closure__)
                setattr(new_function, 'platform', platform)
                setattr(base_class, new_name, new_function)
            delattr(base_class, name)

    return base_class

Dengan ini, yang harus saya lakukan adalah menambahkan dekorator sederhana @sauce_labs () ke setiap TestCase lama yang biasa, dan sekarang ketika menjalankannya, mereka dibungkus dan ditulis ulang, sehingga semua metode pengujian diparameterisasi dan diganti nama. LoginTests.test_login (self) berjalan sebagai LoginTests.test_login_internet_explorer_10.0 (self), LoginTests.test_login_internet_explorer_11.0 (self), dan LoginTests.test_login_firefox_43.0 (self), dan masing-masing memiliki browser untuk menentukan sendiri parameter apa saja / parameter apa saja yang menentukan sendiri parameternya. platform untuk melawan, bahkan di LoginTests.setUp, yang sangat penting untuk tugas saya karena di situlah koneksi ke SauceLabs diinisialisasi.

Bagaimanapun, saya harap ini bisa membantu seseorang yang ingin melakukan parameterisasi "global" yang serupa dari tes mereka!


0

Jawaban berbasis metaclass masih berfungsi di Python3, tetapi alih-alih __metaclass__atribut yang harus digunakan metaclassparameter, seperti pada:

class ExampleTestCase(TestCase,metaclass=DocTestMeta):
    pass

0

Pemrograman meta memang menyenangkan, tetapi bisa digunakan. Sebagian besar solusi di sini mempersulit:

  • selektif meluncurkan tes
  • arahkan kembali ke kode yang diberikan nama tes

Jadi, saran pertama saya adalah mengikuti jalur sederhana / eksplisit (berfungsi dengan semua test runner):

import unittest

class TestSequence(unittest.TestCase):

    def _test_complex_property(self, a, b):
        self.assertEqual(a,b)

    def test_foo(self):
        self._test_complex_property("a", "a")
    def test_bar(self):
        self._test_complex_property("a", "b")
    def test_lee(self):
        self._test_complex_property("b", "b")

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

Karena kita tidak boleh mengulangi sendiri, saran kedua saya didasarkan pada jawaban @ Javier: merangkul pengujian berbasis properti. Perpustakaan hipotesis:

  • adalah "lebih licik berbelit-belit tentang generasi kasus uji daripada kita manusia biasa"
  • akan memberikan contoh-contoh sederhana
  • bekerja dengan semua test runner
  • memiliki banyak fitur menarik lainnya (statistik, output tes tambahan, ...)

    kelas TestSequence (unittest.TestCase):

    @given(st.text(), st.text())
    def test_complex_property(self, a, b):
        self.assertEqual(a,b)

Untuk menguji contoh spesifik Anda, cukup tambahkan:

    @example("a", "a")
    @example("a", "b")
    @example("b", "b")

Untuk menjalankan hanya satu contoh tertentu, Anda dapat mengomentari contoh-contoh lain (contoh yang diberikan akan dijalankan terlebih dahulu). Anda mungkin ingin menggunakan @given(st.nothing()). Pilihan lain adalah mengganti seluruh blok dengan:

    @given(st.just("a"), st.just("b"))

Oke, Anda tidak memiliki nama tes yang berbeda. Tapi mungkin Anda hanya perlu:

  • nama deskriptif dari properti yang diuji.
  • input mana yang menyebabkan kegagalan (contoh pemalsuan).

Contoh yang lebih lucu


0

Sangat terlambat ke pesta, tapi aku kesulitan membuat ini berhasil setUpClass.

Inilah versi jawaban @ Javier yang memberikan setUpClassakses ke atribut yang dialokasikan secara dinamis.

import unittest


class GeneralTestCase(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        print ''
        print cls.p1
        print cls.p2

    def runTest1(self):
        self.assertTrue((self.p2 - self.p1) == 1)

    def runTest2(self):
        self.assertFalse((self.p2 - self.p1) == 2)


def load_tests(loader, tests, pattern):
    test_cases = unittest.TestSuite()
    for p1, p2 in [(1, 2), (3, 4)]:
        clsname = 'TestCase_{}_{}'.format(p1, p2)
        dct = {
            'p1': p1,
            'p2': p2,
        }
        cls = type(clsname, (GeneralTestCase,), dct)
        test_cases.addTest(cls('runTest1'))
        test_cases.addTest(cls('runTest2'))
    return test_cases

Keluaran

1
2
..
3
4
..
----------------------------------------------------------------------
Ran 4 tests in 0.000s

OK

0

Hanya untuk melempar solusi lain ke dalam campuran;)

Ini secara efektif sama parameterizeddengan yang disebutkan di atas, tetapi khusus untuk unittest:

def sub_test(param_list):
    """Decorates a test case to run it as a set of subtests."""

    def decorator(f):

        @functools.wraps(f)
        def wrapped(self):
            for param in param_list:
                with self.subTest(**param):
                    f(self, **param)

        return wrapped

    return decorator

Contoh penggunaan:

class TestStuff(unittest.TestCase):
    @sub_test([
        dict(arg1='a', arg2='b'),
        dict(arg1='x', arg2='y'),
    ])
    def test_stuff(self, a, b):
        ...

-1

Selain menggunakan setattr, kita dapat menggunakan load_tests sejak python 3.2. Silakan merujuk ke posting blog blog.livreuro.com/en/coding/python/how-to-generate-discoverable-unit-tests-in-python-dynamically/

class Test(unittest.TestCase):
    pass

def _test(self, file_name):
    open(file_name, 'r') as f:
        self.assertEqual('test result',f.read())

def _generate_test(file_name):
    def test(self):
        _test(self, file_name)
    return test

def _generate_tests():
    for file in files:
        file_name = os.path.splitext(os.path.basename(file))[0]
        setattr(Test, 'test_%s' % file_name, _generate_test(file))

test_cases = (Test,)

def load_tests(loader, tests, pattern):
    _generate_tests()
    suite = TestSuite()
    for test_class in test_cases:
        tests = loader.loadTestsFromTestCase(test_class)
        suite.addTests(tests)
    return suite

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

-1

Berikut ini adalah solusi saya. Saya menemukan ini berguna ketika: 1. Harus bekerja untuk unittest.Testcase dan unittest temukan 2. Memiliki serangkaian tes yang akan dijalankan untuk pengaturan parameter yang berbeda. 3. Sangat sederhana tidak ada ketergantungan pada impor paket lain unittest

    class BaseClass(unittest.TestCase):
        def setUp(self):
            self.param = 2
            self.base = 2

        def test_me(self):
            self.assertGreaterEqual(5, self.param+self.base)

        def test_me_too(self):
            self.assertLessEqual(3, self.param+self.base)



     class Child_One(BaseClass):
        def setUp(self):
            BaseClass.setUp(self)
            self.param = 4


     class Child_Two(BaseClass):
        def setUp(self):
            BaseClass.setUp(self)
            self.param = 1

Ini tidak menjawab pertanyaan, yaitu tentang membuat tes dengan cepat.
lenz

-1
import unittest

def generator(test_class, a, b,c,d,name):
    def test(self):
        print('Testexecution=',name)
        print('a=',a)
        print('b=',b)
        print('c=',c)
        print('d=',d)

    return test

def add_test_methods(test_class):
    test_list = [[3,3,5,6, 'one'], [5,5,8,9, 'two'], [0,0,5,6, 'three'],[0,0,2,3,'Four']]
    for case in test_list:
        print('case=',case[0], case[1],case[2],case[3],case[4])
        test = generator(test_class, case[0], case[1],case[2],case[3],case[4])
        setattr(test_class, "test_%s" % case[4], test)


class TestAuto(unittest.TestCase):
    def setUp(self):
        print ('Setup')
        pass

    def tearDown(self):
        print ('TearDown')
        pass

add_test_methods(TestAuto)

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

sepertinya Anda kehilangan format di sana. sangat sulit untuk dibaca
Arturo
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.