Существует ли минимальный стиль для unittests в Python?

Я задаюсь вопросом, что люди методов используют для упрощения 'размера' кода, используемого для поблочного тестирования. Например, я пытался упорядочить объект класса и тестировал объект marshal'ed (но это предполагает, что маршал работает правильно).

Рассмотрите класс

import unittest
class Nums(object):
    def __init__(self, n1_, n2_, n3_):
        self.n1, self.n2, self.n3 = n1_, n2_, n3_
def marshal(self):
    return "n1 %g, n2 %g, n3 %g"%(self.n1,self.n2,self.n3)

и затем базирующийся маршалинг, список базирующиеся, и нормальные тесты

class NumsTests(unittest.TestCase):
    def setUp(self):
        self.nu = Nums(10,20,30)
    def test_init1(self):
        self.assertEquals(self.nu.marshal(),"n1 %g, n2 %g, n3 %g"%(10,20,30))
    def test_init2(self):
        self.assertEquals([self.nu.n1,self.nu.n2,self.nu.n3],[10,21,31])
    def test_init3(self):
        self.assertEquals(self.nu.n1,10)
        self.assertEquals(self.nu.n2,21)
        self.assertEquals(self.nu.n3,31)

которые дают следующие ошибки (так как, 20! =21 и 30! =31, мой тест имеет плохую инициализацию, или условия испытания являются неправильными),

AssertionError: 'n1 10, n2 20, n3 30' != 'n1 10, n2 21, n3 31'

AssertionError: [10, 20, 30] != [10, 21, 31]

AssertionError: 20 != 21

Первые и вторые сообщения об ошибках трудно понять (так как необходимо проанализировать строку или список). Однако 3-я техника быстро взрывается в объеме кода, используемого к объектам испытательного комплекса.

Существует ли лучший способ упростить модульные тесты, не создавая трудные сообщения об ошибках? И, без в зависимости от правдивости функции маршала?

[измененный test_marshal кому: marshal]

5
задан Katya B 22 June 2010 в 15:44
поделиться

2 ответа

Для тестирования инициализации я рекомендую , а не тестирование с помощью вызова функции marshal () . Затем вам нужно не только определить, какой инициализатор вышел из строя, вам также нужно выяснить, не работает ли это ваша инициализация или сама функция маршалинга. «Минимальный стиль» для модульных тестов, который я бы порекомендовал, состоит в том, чтобы сузить фокус того, что вы тестируете в любой точке тестирования, насколько это возможно.

Если бы мне действительно пришлось протестировать инициализацию целой группы полей, я мог бы провести рефакторинг почти так же, как Эли:

class MyTestCase(unittest.TestCase):
    def assertFieldsEqual(self, obj, expectedFieldValDict):
        for fieldName, fieldVal in expectedFieldValDict.items():
            self.assertFieldEqual(obj, fieldName, fieldVal)
    def assertFieldEqual(self, obj, fieldName, expectedFieldVal):
        actualFieldVal = getattr(obj, fieldName)
        if expectedFieldVal != actualFieldVal:
            msg = "Field %r doesn't match: expected %r, actual %r" % \
                (fieldName, expectedFieldVal, actualFieldVal)
            self.fail(msg)

class NumsTests(MyTestCase):
    def setUp(self):
        self.initFields = {'n1': 10, 'n2': 20, 'n3': 30}
        self.nums = Nums(**initFields)
    def test_init(self):
        self.assertFieldsEqual(self.nums, self.initFields)

«Добрый день, - я слышу, как вы говорите, - это много кода!» Ну да, но различия заключаются в следующем:

  • assertFieldsEqual и assertFieldEqual - это функции многократного использования, которые были абстрагированы в общий класс тестовых примеров, который могут повторно использоваться другими вашими тестовыми примерами.
  • Сообщение об ошибке точно описывает, что происходит.

Когда приходит время протестировать вашу маршалированную функцию, вы можете просто сделать это:

def test_marshal(self):
    expected = '...'
    self.assertEqual(expected, self.nums.marshal())

Однако при сравнении строк я предпочитаю метод, который сообщает мне, где именно расходятся строки. Для многострочных строк теперь есть метод для этого в Python 2.7, но в прошлом я использовал собственные методы для этой цели.

2
ответ дан 14 December 2019 в 18:58
поделиться

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

import unittest

class Nums(object):
    FORMAT = "n1 %g, n2 %g, n3 %g"  # make this a variable for easy testing

    def __init__(self, n1, n2, n3):
        self.n1, self.n2, self.n3 = n1, n2, n3  # no underscores necessary

    def test_marshal(self):
        return self.FORMAT % (self.n1, self.n2, self.n3)


class NumsTests(unittest.TestCase):
    def setUp(self):
        self.nums = [10, 20, 30]    # make a param list variable to avoid duplication
        self.nu = Nums(*self.nums)  # Python "apply" syntax
        self.nu_nums = [self.nu.n1, self.nu.n2, self.nu.n3]  # we'll use this repeatedly

    def test_init1(self):
        self.assertEquals(self.nu.test_marshal(), self.nu.FORMAT % self.nums )

    def test_init2(self):
        self.assertEquals(self.nums, self.nu_nums)

    def test_init3(self):
        for reference, test in zip(self.nums, self.nu_nums):
            self.assertEquals(reference, test)

См. http://docs.python.org/library/functions.html#apply для объяснения синтаксиса apply. использованный выше.

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

Что касается сбивающих с толку сообщений об ошибках, я полагаю, это зависит от того, сколько подробностей вам нужно. Лично тот факт, что мои модульные тесты дают мне строку кода и значения, которые ожидались и отсутствовали, как правило, довольно ясно показывает, что пошло не так.Однако, если вам ДЕЙСТВИТЕЛЬНО нужно что-то, в котором конкретно указывается, какое поле неверно, а не только значения, которые не совпадают, И вы хотите избежать дублирования кода, вы можете написать что-то вроде этого:

class NumsTests(unittest.TestCase):
    def setUp(self):
        self.nums = {"n1": 10, "n2": 20, "n3": 30}  # use a dict, not a list
        self.nu = Nums(**self.nums)                 # same Python "apply" syntax

    # test_init1 and test_init2 omitted for space

    def test_init3(self):
        for attr,val in self.nums.items():
            self.assertEqual([attr, val], [attr, getattr(self.nu, val)])

Если у вас когда-либо были несоответствующие значения -соответствие значений, теперь вы будете получать ошибки, которые выглядят как

AssertionError: ["n1", 10] != ["n1", 11]

, и, таким образом, вы сразу узнаете, какое поле не соответствует, вместо того, чтобы рассуждать об этом на основе того, какие значения были. Этот подход по-прежнему сохраняет проблему расширения кода, поскольку test_init3 останется того же размера независимо от того, сколько параметров вы добавите в свой класс Nums .

Обратите внимание, что для этого использования getattr требуется, чтобы ваши параметры __ init __ имели то же имя, что и поля в классе num, например они должны называться n1 , а не n1_ и т. д. Альтернативным подходом было бы использование атрибута __ dict__, как описано здесь .

3
ответ дан 14 December 2019 в 18:58
поделиться
Другие вопросы по тегам:

Похожие вопросы: