Как проверить одно и то же утверждение для большого количества данных - PullRequest
12 голосов
/ 12 октября 2010

Я использую модуль python unittest для выполнения ряда тестов;однако, это очень повторяющееся.

У меня есть много данных, которые я хочу снова и снова проходить через один и тот же тест, проверяя правильность.Тем не менее, я должен определить тест для каждого.

Например, я хочу сделать что-то похожее на это.Я знаю, что мог бы сделать это с помощью генератора (нашел его в предыдущей теме здесь).Но есть ли альтернативы, может быть, даже с использованием другого модуля тестирования?

Любые предложения будут хорошими.


import unittest

class TestData(unittest.TestCase):
    def testNumbers(self):
        numbers = [0,11,222,33,44,555,6,77,8,9999]
        for i in numbers:
            self.assertEqual(i, 33)

Ответы [ 7 ]

10 голосов
/ 13 октября 2010

Пример кода для решения, предложенного Биллом Грибблом, может выглядеть так:

import unittest

class DataTestCase(unittest.TestCase):
    def __init__(self, number):
        unittest.TestCase.__init__(self, methodName='testOneNumber')
        self.number = number

    def testOneNumber(self):
        self.assertEqual(self.number, 33)

    def shortDescription(self):
        # We need to distinguish between instances of this test case.
        return 'DataTestCase for number %d' % self.number


def get_test_data_suite():
    numbers = [0,11,222,33,44,555,6,77,8,9999]
    return unittest.TestSuite([DataTestCase(n) for n in numbers])

if __name__ == '__main__':
    testRunner = unittest.TextTestRunner()
    testRunner.run(get_test_data_suite())
5 голосов
/ 12 октября 2010

Вы можете рассмотреть возможность использования класса unittest.TestSuite, который позволит вам динамически создавать набор экземпляров unittest.TestCase, которые будут запускаться отдельно. Ваш подкласс unittest.TestCase должен определять только один метод тестирования, при этом класс принимает параметр конструкции, передавая значение для проверки для данного конкретного экземпляра.

4 голосов
/ 09 августа 2016

Библиотека ddt была создана для того, чтобы точно решить, что вы просите unittest [*].

Например:

import ddt
import unittest

@ddt.ddt
class EvalTests(unittest.TestCase):

    @ddt.data(
            ('1', 1),
            ('1 == 1',  True),
            ('1 == 2',  False),
            ('1 + 2',   4),  ## This will fail
    )
    def test_eval_expressions(self, case):
        expr, exp_value = case
        self.assertEqual(eval(expr), exp_value)

Ипри его запуске вы получаете 4 TestCases вместо одного:

$ python -m unittest  -v  test_eval.py
test_eval_expressions_1___1___1_ (test_eval.EvalTests) ... ok
test_eval_expressions_2___1__1___True_ (test_eval.EvalTests) ... ok
test_eval_expressions_3___1__2___False_ (test_eval.EvalTests) ... ok
test_eval_expressions_4___1_2___4_ (test_eval.EvalTests) ... FAIL

======================================================================
FAIL: test_eval_expressions_4___1_2___4_ (test_eval.EvalTests)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/usr/lib/python/lib/site-packages/ddt.py", line 129, in wrapper
    return func(self, *args, **kwargs)
  File "/Work/test_eval.py", line 15, in test_eval_expressions
    self.assertEqual(eval(expr), exp_value)
AssertionError: 3 != 4

----------------------------------------------------------------------
Ran 4 tests in 0.002s

FAILED (failures=1)

Обратите внимание, что ddt пытается найти имена для сгенерированных TC.

Installэто с pip:

pip install ddt

[*] То же решение для pythonic pytest framework (pytest.mark.parametrize) интегрировано в основной инструмент и стоит перейти на pytest только для этой функцииодин.

2 голосов
/ 15 октября 2010

В другом посте я наткнулся на Тесты на носу Он больше подходит для тестирования на основе данных.


class Test_data():
    def testNumbers():
        numbers = [0,11,222,33,44,555,6,77,8,9999]
        for i in numbers:
            yield checkNumber, num

def checkNumber(num):
    assert num == 33

Приведенный выше код делает то же самое, что и мой первый пост.Импорт не требуется, просто напишите класс Python.

Вы выполняете тесты, набирая:

nosetests filename

2 голосов
/ 12 октября 2010

Проблема с запуском утверждений в цикле состоит в том, что, если одно из утверждений завершается неудачно, вы не знаете, какое значение вызвало его (в вашем примере это произойдет сбой на 0, но вы этого не знаете пока не отладишь) С другой стороны, повторение self.assertEqual(i, 33) является еще худшей идеей, потому что оно вводит дублирование кода.

В моем тесте я создаю простую внутреннюю функцию с коротким именем внутри теста и вызываю ее с другими аргументами. Итак, ваша функция будет выглядеть так:

import unittest

class TestData(unittest.TestCase):
    def testNumbers(self):
        def eq(i):
            self.assertEqual(i, 33)
        eq(0)
        eq(11)
        eq(222)
        eq(33)
        eq(44)
        eq(555)
        ... 

Таким образом, когда утверждение не выполняется для 0, вы сразу же видите его на трассе стека, напечатанной модулем unittest.

1 голос
/ 22 января 2019

Начиная с Python 3.4, вы можете использовать unittest.TestCase.subTest(msg=None, **params) менеджер контекста ( документация ). Это позволит вам достичь того, чего вы хотите, добавив всего одно утверждение.

Вот ваш пример, модифицированный для использования subTest()

import unittest

class TestData(unittest.TestCase):
    def testNumbers(self):
        numbers = [0, 11, 222, 33, 44, 555, 6, 77, 8, 9999]
        for i in numbers:
            with self.subTest(i=i):  # added statement
                self.assertEqual(i, 33)
0 голосов
/ 16 марта 2017

Выделение этого ответа, что не совсем сработало для меня.Там, где я не имею дело с большими объемами данных, мне нужно было запускать одни и те же тесты с разными входами.В следующих тестах используются методы create_a и create_b, которые я хочу настроить.

Требуется запустить оба теста с одинаковой настройкой.

class Tests(unittest.TestCase):

    def test_a_uses_b(self):
        a = create_a()
        b = create_b()
        a.b = b
        self.assertIs(b.a, a)

    def test_b_uses_a(self):
        a = create_a()
        b = create_b()
        b.a = a
        self.assertIs(a.b, b)

Создание экземпляров TestSuite и TestCase самостоятельно, в обход загрузчика тестов, привело к ошибке, поскольку ожидалосьодин метод, называемый runTest.

Результат был таким:

class Tests(unittest.TestCase):

    def __init__(self, create_a, create_b):
        super().__init__()
        self.create_b = create_b
        self.create_a = create_a

    def test_a_uses_b(self):
        a = self.create_a()
        b = self.create_b()
        a.b = b
        self.assertIs(b.a, a)

    def test_b_uses_a(self):
        a = self.create_a()
        b = self.create_b()
        b.a = a
        self.assertIs(a.b, b)


class TestPair1(Tests):
    def __init__(self):
        super().__init__(create_a1, create_b1)


class TestPair2(Tests):
    def __init__(self):
        super().__init__(create_a2, create_b2)
...