我正在尝试为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一起使用时?
答案 0 :(得分:3)
“一般情况下,您是否会使用此功能,知道其限制......?”
坦率地说 - 永远不会。在任何情况下我都不会以这种方式“冻结”某个功能的结果。
用例似乎基于两个错误的想法:(1)单元测试既硬又复杂或昂贵; (2)编写代码可能更简单,“冻结”结果并以某种方式使用冻结结果进行重构。这没用。事实上,冻结错误答案的真正可能性使这个想法变得糟糕。
首先,关于“一致性与正确性”。使用简单的映射比使用复杂的装饰器集更容易保留。
执行此操作而不是编写冻结装饰器。
print "frozen_f=", dict( (i,f(i)) for i in range(100) )
创建的字典对象将完美地用作冻结结果集。没有装饰。没有复杂性可言。
其次,关于“单元测试”。
单元测试的重点是不来“冻结”一些随机结果。单元测试的要点是将实际结果与另一个结果(更简单,更明显,表现不佳)进行比较。通常单元测试比较手工开发的结果。其他时候单元测试使用明显但非常慢的算法来产生一些关键结果。
拥有测试数据的关键不在于它是一个“冻结”的结果。测试数据的关键在于它是一个独立的结果。做得不同 - 有时是由不同的人 - 确认该功能有效。
对不起。在我看来这是一个坏主意;看起来它颠覆了单元测试的意图。
“但是,没人能否认编写测试用例的开销”
实际上,很多人会否认“开销”。在浪费时间和精力的意义上,它不是“开销”。对于我们中的一些人来说,单元测试至关重要。没有它们,代码可能会起作用,但只是偶然。有了他们,我们有充分的证据表明它确实有效;以及它的具体案例。
答案 1 :(得分:0)
您是要实施不变量还是发布条件?
您应该明确指定结果,这将消除您的大多数问题。