【问题标题】:A simple freeze behavior decorator一个简单的冻结行为装饰器
【发布时间】:2010-10-13 19:38:21
【问题描述】:

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

思路如下:

(回应两位cmets)

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

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

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

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

坦率地说,我是一个非常年轻的程序员,如果我是 你,我在这个问题上的话并不真正有价值...... 无论如何,我认为大多数公司/项目都行不通 像这样,并且单元测试主要用于第二个 案例...

换句话说,而不是确保程序是 工作正常,它的目的是检查它是否会 以后也一样。

无需编写测试成本即可满足此需求, 通过使用这个冷冻装饰器。

假设你有一个函数

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

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

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

@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
  • 结果的转储和比较对于所有类型来说都不是微不足道的。现在我正在考虑使用一个函数(我在这里称之为后处理)来解决这个问题。 基本上,我不是存储 res,而是存储 postprocess(res),然后比较 postprocess(res1)==postprocess(res2),而不是比较 res1 res2。 让用户重载预定义的后处理函数很重要。 我的第一个问题是: 您知道检查对象是否可转储的方法吗?

  • 为修饰的函数定义一个键是一件很痛苦的事。在下面的sn-ps 我正在使用功能模块及其名称。 ** 你能想出一个更聪明的方法来做到这一点。 **

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

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

  • 一般来说,你会使用这样的功能吗,知道它的局限性……尤其是在尝试将它与 POO 一起使用时?

【问题讨论】:

  • 你应该详细说明为什么;不清楚为什么你会想要这个而不是单元测试。 -- 另外,当函数故意改变时更新 .frz 文件听起来很痛苦。
  • "一般来说,你会使用这样的功能吗...?"绝不。对不起,但这个描述对我来说毫无意义。用例是模糊的。您只是在文件中腌制函数结果吗?
  • 感谢您的 cmets!让我在主帖中回答。
  • @Deestan : 关于改变,确实很痛苦!!!我还不知道如何让用户简单地“解冻”界面。 (不过,我可以看到一两种方法),如果您对此有任何聪明的想法,我也想听听。

标签: python testing decorator freeze


【解决方案1】:

“一般来说,你会使用这样的功能,知道它的局限性吗...?”

坦率地说——从来没有。

在任何情况下我都不会以这种方式“冻结”函数的结果。

用例似乎基于两个错误的想法:(1) 单元测试要么困难、复杂或昂贵; (2) 编写代码可能更简单,“冻结”结果并以某种方式使用冻结的结果进行重构。这没有帮助。确实,冻结错误答案的可能性非常大,这使这是一个坏主意。

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

这样做而不是编写冻结装饰器。

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

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

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

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

拥有测试数据的意义不在于它是“冻结”的结果。拥有测试数据的意义在于它是一个独立的结果。以不同的方式完成——有时由不同的人——确认该功能有效。

对不起。在我看来,这不是一个好主意。看来颠覆了单元测试的初衷。


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

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

【讨论】:

  • 非常感谢您的意见。 > 单元测试的重点是将真实结果与另一个开发的结果进行比较(更简单、更明显、性能不佳的方式)。对规范部分是常规的简单函数进行单元测试呢?例如为错误的参数引发或返回 None
  • 对于“微不足道”的单元测试(例如,arg 验证),您正在手动计算答案。手动开发结果非常容易。但这与手动开发预期结果的基本模式相同。
【解决方案2】:

您是要实现不变量还是后置条件?

您应该明确指定结果,这将解决大部分问题。

【讨论】:

  • > 您是否希望实现不变量或后置条件?不是真的...这更多是关于检查您的函数在一年内的行为是否相同。但这是个好主意。我已经在另一个项目中使用它了。
猜你喜欢
  • 2011-08-02
  • 2011-09-03
  • 1970-01-01
  • 2016-02-11
  • 2015-11-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多