С Python unittest, как я могу создать и использовать «вызываемый объект, который возвращает набор тестов»? - PullRequest
5 голосов
/ 28 июня 2009

Я изучаю Python и пытаюсь понять больше деталей модуля unittest в Python. Документация включает в себя следующее:

Для простоты выполнения тестов, как мы увидим позже, это хорошая идея предоставить в каждом тестовом модуле вызываемый объект, который возвращает предварительно построенный тест люкс:

def suite():
    suite = unittest.TestSuite()
    suite.addTest(WidgetTestCase('testDefaultSize'))
    suite.addTest(WidgetTestCase('testResize'))
    return suite

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

import unittest

def average(values):
    return sum(values) / len(values)

class MyTestCase(unittest.TestCase):
    def testFoo(self):
        self.assertEqual(average([10,100]),55)

    def testBar(self):
        self.assertEqual(average([11]),11)

    def testBaz(self):
        self.assertEqual(average([20,20]),20)

    def suite():
        suite = unittest.TestSuite()
        suite.addTest(MyTestCase('testFoo'))
        suite.addTest(MyTestCase('testBar'))
        suite.addTest(MyTestCase('testBaz'))
        return suite

if __name__ == '__main__':
    # s = MyTestCase.suite()
    # TypeError: unbound method suite() must be called 
    # with MyTestCase instance as first argument

    # s = MyTestCase.suite(MyTestCase())
    # ValueError: no such test method in <class '__main__.MyTestCase'>: runTest

    # s = MyTestCase.suite(MyTestCase('testFoo'))
    # TypeError: suite() takes no arguments (1 given)

Следующее «сработало», но кажется неловким, и мне потребовалось изменить сигнатуру метода suite() на «def suite(self):».

s = MyTestCase('testFoo').suite()
unittest.TextTestRunner().run(s)

Ответы [ 5 ]

6 голосов
/ 28 июня 2009

Самое первое полученное вами сообщение об ошибке имеет смысл и многое объясняет.

print MyTestCase.suite # <unbound method MyTestCase.suite>

Вольный . Это означает, что вы не можете вызвать его, если не связываете его с экземпляром. Это на самом деле то же самое для MyTestCase.run:

print MyTestCase.run # <unbound method MyTestCase.run>

Возможно, сейчас вы не понимаете, почему не можете позвонить suite, но, пожалуйста, оставьте это в стороне. Вы бы попытались позвонить run в классе, как указано выше? Что-то вроде:

MyTestCase.run() # ?

Наверное, нет, верно? Не имеет смысла писать это, и это не будет работать, потому что run является методом экземпляра, и для работы требуется экземпляр self. Похоже, что Python «понимает» suite так же, как он понимает run как несвязанный метод.

Посмотрим, почему:

Если вы попытаетесь вывести метод suite из области видимости класса и определить его как глобальную функцию, он просто будет работать:

import unittest

def average(values):
    return sum(values) / len(values)

class MyTestCase(unittest.TestCase):
    def testFoo(self):
        self.assertEqual(average([10,100]),55)

    def testBar(self):
        self.assertEqual(average([11]),11)

    def testBaz(self):
        self.assertEqual(average([20,20]),20)

def suite():
    suite = unittest.TestSuite()
    suite.addTest(MyTestCase('testFoo'))
    suite.addTest(MyTestCase('testBar'))
    suite.addTest(MyTestCase('testBaz'))
    return suite

print suite() # <unittest.TestSuite tests=[<__main__.MyTestCase testMethod=testFoo>, <__main__.MyTestCase testMethod=testBar>, <__main__.MyTestCase testMethod=testBaz>]>

Но вы не хотите, чтобы это выходило за рамки класса, потому что вы хотите позвонить MyTestCase.suite()

Вы, вероятно, думали, что, поскольку suite является своего рода "статическим" или независимым от экземпляра, не имеет смысла ставить аргумент self, не так ли? Это верно.

Но если вы определите метод внутри класса Python, Python будет ожидать, что этот метод будет иметь аргумент self в качестве первого аргумента. Простое пропускание аргумента self не делает ваш метод static автоматически. Если вы хотите определить «статический» метод, вы должны использовать декоратор staticmethod :

@staticmethod
def suite():
    suite = unittest.TestSuite()
    suite.addTest(MyTestCase('testFoo'))
    suite.addTest(MyTestCase('testBar'))
    suite.addTest(MyTestCase('testBaz'))
    return suite

Таким образом, Python рассматривает MyTestCase не как метод экземпляра, а как функцию:

print MyTestCase.suite # <function suite at 0x...>

И, конечно, теперь вы можете позвонить MyTestCase.suite(), и это сработает, как и ожидалось.

if __name__ == '__main__':
    s = MyTestCase.suite()
    unittest.TextTestRunner().run(s) # Ran 3 tests in 0.000s, OK
1 голос
/ 22 января 2010

Как уже упоминалось, документация ссылается на suite () как метод в модуле, и unittest (как ни странно), похоже, не имеет какого-либо специального распознавания для этого метода, поэтому вам нужно вызывать его явно. Однако если вы используете инструмент с именем " testoob ", он автоматически вызывает метод suite () (если вы укажете аргумент defaultTest = "suite" для его main ()) и добавит несколько других функций сверху базового пакета unittest. Он также предоставляет опции для генерации XML-файлов, которые включают собранные stdout и stderr из этих тестов (что является большим плюсом для автоматизированных тестов), а также может генерировать HTML-отчеты (хотя вам необходимо будет установить дополнительные пакеты). Я не смог найти способ автоматически обнаружить все тесты, которые, как утверждает нос, поддерживает, поэтому нос, вероятно, является лучшим вариантом.

1 голос
/ 28 июня 2009

Важно отметить, что nose - это инструмент, который действительно упрощает выполнение тестов. Он позволяет вам точно указать, какие тесты запускать из командной строки, а также проходить через каждый каталог и запускать каждый тест.

1 голос
/ 28 июня 2009

В документации говорится, что suite() должна быть функцией в модуле, а не методом в вашем классе. Похоже, что это просто удобная функция, поэтому вы можете объединить наборы тестов для многих модулей:

alltests = unittest.TestSuite([
  my_module_1.suite(),
  my_module_2.suite(),
])

Все ваши проблемы с вызовом вашей функции связаны с тем, что она является методом в классе, но не написана как метод. Параметр self является «текущим объектом» и требуется для методов экземпляра. (Например, a.b(1, 2) концептуально совпадает с b(a, 1, 2).) Если метод работает с классом, а не с экземплярами, прочитайте о classmethod. Если он просто сгруппирован с классом для удобства, но не работает ни с классом, ни с экземплярами, прочитайте о staticmethod. Ничто из этого не поможет вам использовать unittest, но может помочь объяснить, почему вы увидели, что сделали.

0 голосов
/ 14 апреля 2011

Я бы предложил использовать следующее. Это избавит вас от необходимости вручную вводить все тесты, которые вы добавляете по ходу работы.

def suite():
    suite = unittest.TestLoader().loadTestsFromTestCase(Your_Test_Case_Class)
    return suite

Это дает вам гибкость для выполнения тестов в одном и том же модуле, определяя:

if __name__ == "__main__":
     suite()

И если вы хотите связать свои наборы в другой модуль, например, test_suite.py (например), то это можно сделать следующим образом:

импортировать имя test_module импортный тест

if __name__=="__main__":
    suite1=test_module_name.suite()
    ...
    ...
    alltests = unittest.TestSuite([suite1,suite2])

Теперь, как запустить ваши тесты. Обычно я использую более простой способ выполнения, выполняя эту команду на уровне пакета, и unittest автоматически обнаруживает тесты:

python -m unittest discover

или

nosetests 

Предостережение: Unittest работает в X раз быстрее, чем тестирование носа, так что теперь это зависит от предпочтений разработчиков, особенно если они использовали сторонние плагины для тестирования носа и хотят продолжать его использовать.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...