Image

Imagesuguby wrote in Imageru_python

Метапрограммирование в питоне

наткнулся на отличную статью про метапрограммирование в питоне http://habrahabr.ru/blogs/python/64359/ - всем советую :)


решил использовать - в юнит-тестах:

class MyTestCase:

    def __init__(self, test_funct, testing_param_set, test_class = None):

        # тестируемая функция и набор параметров к ней
        self.main_funct = test_funct
        self.testing_param_set = testing_param_set

        # имя тестируемой функции
        self.test_funct_name = self.main_funct.__name__
        # имя файла для сохранения эталонных данных
        self.etalon_file_name = self.test_funct_name + '.etalon'

        if test_class:
            # пользователь уже создал класс - потомок unittest.TestCase - со своими функциями, юзаем его
            self.test_class = test_class
        else:
            # юзаем пустой класс - потомок unittest.TestCase
            self.test_class = TestClass

        # набиваем класс тестовыми функциями
        self.make_test_case()

    def make_test_case_name(self, case_params):
        """
            генерит уникальное имя тест-кейса для данного набора параметров, которое будет использовано в эталоне и вызове теста
        """
        return self.test_funct_name + '_' + '_'.join(map(str, case_params))

    def test_case_funct_factory(self, case_name, case_params):
        """
            фабрика функций-тест-кейсов
        """
        def funct(self_in_test):
    #        print etalon[case_name], main_funct(case_params)
            self_in_test.assertEqual( self.main_funct(case_params), self.etalon[case_name] )
        return funct

    def make_test_case(self):
        """
            создать функции тестирования на основе списка параметров
            с помощью метапрограммирования (см. http://habrahabr.ru/blogs/python/64359/)
        """
        for case_params in self.testing_param_set:
            case_name = self.make_test_case_name(case_params) # имя тест-кейса
            test_case_funct = self.test_case_funct_factory(case_name, case_params) # генеририм тестовую функцию
            setattr(self.test_class, 'test_'+case_name, test_case_funct ) # добавляем сгенерированную функцию как метод класса

if __name__ == '__main__':

    test = MyTestCase(
        test_funct = getTEMarketsList, 
        testing_param_set = [
                [None, None, 'all', 0],
                [None, None, 'all', 1],
                [None, None, 'inday', 0],
                [None, None, 'inday', 1],
                ['currency', None, 'all', 0],
                ['stock', None, 'all', 1],
                ['stock', None, 'offday', 0],
            ], 
        test_class = None
    )

    test.load_etalon() # загружаем эталоны значений функции при данном наборе параметров

    test.reinit_database( # пересоздаем таблички в тестовой БД
        database='xxx',
        tables = ['yyy', 'zzz']
    )

    test.run() # пускаем тест

(навороты для того что бы передать список вариантов (набор) параметров тестируемой фукции и сгенерить класс юнит-теста с функциями, тестирующими каждая свой сет параметров)

Но! Создать пустой класс, потомок unittest.TestCase, на лету не получилось :(
(вот еще мегастатья http://www.ibm.com/developerworks/ru/library/l-pymeta/index.html)
from new import classobj
self.test_class = classobj('MyNameClass', (unittest.TestCase, ), {})

класс создался, но не породился от unittest.TestCase... поэтому просто сделал пустой класс и испльзовал его. И теперь в сообщениях об ошибках в тест-кейсе выдается это фейковое имя:
======================================================================
FAIL: test_getTEMarketsList_currency_None_all_0 (__main__.TestClass)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "getTEMarketsList.py", line 62, in funct
    self_in_test.assertEqual( self.main_funct(case_params), self.etalon[case_name] )
AssertionError: [['currency', 'seltttt'], ['currency', 'basket']] != [['currency', 'selt'], ['currency', 'basket']]

см строку __main__.TestClass. С этим конечно тоже можно жить, но...

Возник вопрос: а можно ли изменить имя класса на лету?
Другой вопрос - не слишком ли я усложнил? ;) может есть простые решения для автоматического юнит-тестирования на основе наборов входных параметров?