一个简单的冻结行为装饰器

时间:2009-03-17 11:08:54

标签: python testing decorator freeze

我正在尝试为Python编写一个冻结装饰器。

这个想法如下:

(回应两条评论)

我可能错了,但我认为有两个主要用途 测试用例。

  • 一个是测试驱动的开发: 理想情况下,开发人员在编写代码之前编写案例。 它通常有助于定义架构,因为这个学科 强制在开发之前定义真实的接口。 人们甚至可以认为在某些情况下是那个人 dev之间的调度工作正在编写测试用例和 用它来有效地说明他的想法。 我没有任何使用此类测试用例的经验。

  • 第二个是所有项目都体面的想法 大小和几个程序员正在遭受破坏的代码。 用于工作的东西可能会因改变而破裂 这看起来像是一个无辜的重构。 虽然良好的架构,组件之间的松散耦合可能 帮助对抗这种现象;你会睡得更好 在晚上,如果你已经写了一些测试用例,以确保 什么都不会破坏你的程序的行为。

然而, 没有人可以否认编写测试用例的开销。在里面 第一种情况可能会争辩说,测试用例实际上是指导性的 发展,因此不应被视为开销。

坦率地说,我是一个非常年轻的程序员,如果我是 你,我对这个问题的看法并不是很有价值...... 无论如何,我认为大多数公司/项目都不起作用 像这样,单元测试主要用在第二个 情况下...

换句话说,而不是确保该计划 工作正常,它的目标是检查它会 在将来也会这样做。

无需编写测试费用即可满足这一需求, 通过使用这个冷冻装饰。

假设你有一个功能

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

非常好,您想将其重写为优化版本。 这是一个大项目的一部分。您希望它返回相同的结果 为了一些价值。 可以使用一些,而不是经历测试案例的痛苦 一种冷冻装饰。

第一次运行装饰器的东西, 装饰器使用定义的args运行函数(低于0和7) 并将结果保存在地图中(f - > args - >结果)

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

下次执行程序时,装饰器将加载此地图并检查 这些args的这个函数的结果没有改变。

我已经快速写了装饰器(见下文),但是却有一些问题 我需要你的建议...

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而是存储后处理(res),我比较后处理(res1)== postprocess(res2),而不是比较res1 res2。 让用户重载预定义的后处理功能非常重要。 我的第一个问题是: 您是否知道检查对象是否可转储的方法?

  • 为装饰的功能定义一个键很痛苦。在以下片段中 我正在使用功能模块及其名称。 **你能想到一个更聪明的方法吗? **

  • 下面的代码段有点工作,但在测试和录制时打开和关闭文件。这只是一个愚蠢的原型......但你知道一个很好的方法来打开文件,处理所有功能的装饰器,关闭文件......

  • 我打算为此添加一些功能。例如,添加可能性来定义 一个可迭代的浏览一组参数,记录实际使用的参数等。 你为什么期望这样的装饰师?

  • 一般情况下,您是否会使用这样的功能,知道其局限性...特别是在尝试与POO一起使用时?

2 个答案:

答案 0 :(得分:3)

“一般情况下,您是否会使用此功能,知道其限制......?”

坦率地说 - 永远不会。

在任何情况下我都不会以这种方式“冻结”某个功能的结果。

用例似乎基于两个错误的想法:(1)单元测试既硬又复杂或昂贵; (2)编写代码可能更简单,“冻结”结果并以某种方式使用冻结结果进行重构。这没用。事实上,冻结错误答案的真正可能性使这个想法变得糟糕。

首先,关于“一致性与正确性”。使用简单的映射比使用复杂的装饰器集更容易保留。

执行此操作而不是编写冻结装饰器。

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

创建的字典对象将完美地用作冻结结果集。没有装饰。没有复杂性可言。

其次,关于“单元测试”。

单元测试的重点是来“冻结”一些随机结果。单元测试的要点是将实际结果与另一个结果(更简单,更明显,表现不佳)进行比较。通常单元测试比较手工开发的结果。其他时候单元测试使用明显但非常慢的算法来产生一些关键结果。

拥有测试数据的关键不在于它是一个“冻结”的结果。测试数据的关键在于它是一个独立的结果。做得不同 - 有时是由不同的人 - 确认该功能有效。

对不起。在我看来这是一个坏主意;看起来它颠覆了单元测试的意图。


“但是,没人能否认编写测试用例的开销”

实际上,很多人会否认“开销”。在浪费时间和精力的意义上,它不是“开销”。对于我们中的一些人来说,单元测试至关重要。没有它们,代码可能会起作用,但只是偶然。有了他们,我们有充分的证据表明它确实有效;以及它的具体案例。

答案 1 :(得分:0)

您是要实施不变量还是发布条件?

您应该明确指定结果,这将消除您的大多数问题。