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

Я использую модуль 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)

Ответ 1

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


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

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

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

Ответ 3

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

Ответ 4

Библиотека 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.

Установите его с помощью pip:

pip install ddt

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

Ответ 5

Проблема с запущенными утверждениями в цикле заключается в том, что если одно из утверждений не выполняется, вы не знаете, какое значение вызвало его (в вашем примере это не получится при 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.

Ответ 6

Начиная с 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)

Ответ 7

Отключение этого ответа, что не совсем сработало для меня. Там, где я не имею дело с большими объемами данных, мне нужно было запускать те же тесты с разными входами. Следующие тесты используют методы 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)