Вопрос

Я пытаюсь написать freeze decorator для Python.

Идея заключается в следующем :

(В ответ на два комментария)

Возможно, я ошибаюсь, но я думаю, что есть два основных варианта использования тестовый пример.

  • Одним из них является разработка, основанная на тестировании :В идеале разработчики пишут case перед написанием кода.Обычно это помогает определить архитектуру, потому что эта дисциплина заставляет определять реальные интерфейсы перед разработкой.Можно даже предположить, что в некоторых случаях человек, который отправляет задания между разработчиками, пишет тестовый пример и использует его для эффективной иллюстрации спецификации, которую он имеет в виду.У меня нет никакого опыта использования подобного тестового примера.

  • Вторая идея заключается в том, что все проекты с приличным размером и несколькими программистами страдают от неработающего кода.Что-то, что используется для работы, может сломаться в результате изменения это выглядело как невинный рефакторинг.Несмотря на хорошую архитектуру, слабая связь между компонентами может помочь бороться с этим явлением ;вы будете лучше спать ночью, если вы напишете какой-нибудь тестовый пример, чтобы убедиться что ничто не нарушит поведение вашей программы.

ОДНАКО, Никто не может отрицать накладные расходы на написание тестовых примеров.В первом случае можно утверждать, что тестовый пример фактически направляет разработку и поэтому не должен рассматриваться как накладные расходы.

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

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

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

Допустим, у вас есть функция

def pow(n,k):
    if n == 0:  return 1
    else:       return n * pow(n,k-1)

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

Что-то такое, чтобы при первом запуске декоратора декоратор запускал функцию с определенными аргументами (ниже 0 и 7) и сохранял результат на карте ( f -> аргументы -> результат)

@freeze(2,0)
@freeze(1,3)
@freeze(3,5)
@freeze(0,0)
def pow(n,k):
    if n == 0:  return 1
    else:       return n * pow(n,k-1)

При следующем выполнении программы декоратор загрузит эту карту и проверит что результат этой функции для этих аргументов не изменился.

Я уже быстро написал декоратору (см. Ниже), но у меня возникли несколько проблем по поводу по которым мне нужен ваш совет...

from __future__ import with_statement
from collections import defaultdict
from types import GeneratorType
import cPickle

def __id_from_function(f):
    return ".".join([f.__module__, f.__name__])

def generator_firsts(g, N=100):
    try:
        if N==0:
            return []
        else:
            return  [g.next()] + generator_firsts(g, N-1)
    except StopIteration :
        return []

def __post_process(v):
    specialized_postprocess = [
        (GeneratorType, generator_firsts),
        (Exception,     str),
    ]
    try:
        val_mro = v.__class__.mro()
        for ( ancestor, specialized ) in specialized_postprocess:
            if ancestor in val_mro:
                return specialized(v)
        raise ""
    except:
        print "Cannot accept this as a value"
        return None

def __eval_function(f):
    def aux(args, kargs):
        try:
            return ( True, __post_process( f(*args, **kargs) ) )
        except Exception, e:
            return ( False, __post_process(e) )
    return aux

def __compare_behavior(f, past_records):
    for (args, kargs, result) in past_records:
        assert __eval_function(f)(args,kargs) == result

def __record_behavior(f, past_records, args, kargs):
    registered_args = [ (a, k) for (a, k, r) in past_records ]
    if (args, kargs) not  in registered_args:
        res = __eval_function(f)(args, kargs)
        past_records.append( (args, kargs, res) )

def __open_frz():
    try:
        with open(".frz", "r") as __open_frz:
            return cPickle.load(__open_frz)
    except:
        return defaultdict(list)

def __save_frz(past_records):
    with open(".frz", "w") as __open_frz:
        return cPickle.dump(past_records, __open_frz)


def freeze_behavior(*args, **kvargs):
    def freeze_decorator(f):
        past_records = __open_frz()
        f_id = __id_from_function(f)
        f_past_records = past_records[f_id]
        __compare_behavior(f, f_past_records)
        __record_behavior(f, f_past_records, args, kvargs)
        __save_frz(past_records)
        return f
    return freeze_decorator
  • Сброс и сравнение результатов не является тривиальным для всех типов.Прямо сейчас я подумываю об использовании функции (здесь я называю ее postprocess) для решения этой проблемы.По сути, вместо хранения res я сохраняю postprocess (res) и сравниваю postprocess (res1)==postprocess(res2), вместо сравнения res1 с res2.Важно позволить пользователю перегружать предопределенную функцию постпроцесса.Мой первый вопрос таков :Знаете ли вы способ проверить, является ли объект подлежащим выгрузке или нет?

  • Определение ключа для оформленной функции - это непростая задача.В следующих фрагментах Я использую функциональный модуль и его название.** Можете ли вы придумать более разумный способ сделать это?**

  • Приведенные ниже фрагменты вроде как работают, но открывают и закрывают файл при тестировании и при записи.Это всего лишь глупый прототип...но знаете ли вы хороший способ открыть файл, обработать функцию decorator for all, закрыть файл...

  • Я намерен добавить к этому некоторые функциональные возможности.Например, добавьте возможность определять итерацию для просмотра набора аргументов, записи аргументов из реального использования и т.д.Чего бы вы ожидали от такого декоратора?

  • В общем, стали бы вы использовать такую функцию, зная ее ограничения...Особенно когда пытаешься использовать его с КАКАШКАМИ?

Это было полезно?

Решение

"В общем, стали бы вы использовать такую функцию, зная ее ограниченность...?"

Честно говоря - никогда.

Нет никаких обстоятельств, при которых я бы "заморозил" результаты функции таким образом.

Вариант использования, по-видимому, основан на двух неправильных идеях:(1) что модульное тестирование является либо трудным, либо сложным, либо дорогостоящим;и (2) могло бы быть проще написать код, "заморозить" результаты и каким-то образом использовать замороженные результаты для рефакторинга.Это не помогает.Действительно, очень реальная возможность заморозить неправильные ответы делает это плохой идеей.

Во-первых, о "согласованности противкорректность".Это легче сохранить с помощью простого отображения, чем со сложным набором декораторов.

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

print "frozen_f=", dict( (i,f(i)) for i in range(100) )

Созданный объект dictionary будет отлично работать как замороженный результирующий набор.Никакого декоратора.Никаких сложностей, о которых стоило бы говорить.

Во-вторых, о "модульном тестировании".

Смысл модульного теста в том, не чтобы "заморозить" некоторые случайные результаты.Смысл модульного теста заключается в сравнении реальных результатов с результатами, разработанными другим (более простым, очевидным и неэффективным способом).Обычно модульные тесты сравнивают результаты, разработанные вручную.В других случаях модульные тесты используют очевидные, но ужасно медленные алгоритмы для получения нескольких ключевых результатов.

Смысл наличия тестовых данных не в том, что это "замороженный" результат.Смысл наличия тестовых данных в том, что это независимый результат.Выполняется по-разному - иногда разными людьми, - что подтверждает, что функция работает.

Извините.Мне кажется, это плохая идея;похоже, это подрывает цель модульного тестирования.


"ОДНАКО никто не может отрицать накладных расходов на написание тестовых примеров"

На самом деле, многие люди отрицали бы "накладные расходы".Это не "накладные расходы" в смысле потраченного впустую времени и усилий.Для некоторых из нас unittests имеют важное значение.Без них код может сработать, но только случайно.С ними у нас есть достаточно доказательств того, что это действительно работает;и конкретные случаи, для которых это работает.

Другие советы

Вы хотите реализовать инварианты или опубликовать условия?

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top