【问题标题】:Organizing code for Testing with Constants from a Configuration File使用配置文件中的常量组织测试代码
【发布时间】:2013-06-19 09:55:21
【问题描述】:

我的应用程序从配置文件中读取了许多常量。然后在整个程序的各个地方使用这些常量。这是一个例子:

import ConfigParser

config = ConfigParser.SafeConfigParser()
config.read('my.conf')
DB_HOST = config.get('DatabaseInfo', 'address')
DB_PORT_NUMBER = config.getint('DatabaseInfo', 'port_number')
DB_NUMBER = config.getint('DatabaseInfo', 'db_number')
IN_SERVICE = config.get('ServerInfo', 'in_service')
IN_DATA = config.get('ServerInfo', 'in_data')

等等……

然后,我在整个程序中定义了使用这些常量的函数。这是一个例子:

def connect_to_db():
    return get_connection(DB_HOST, DB_PORT_NUMBER, DB_NUMBER)

但有时,当我在测试或使用 REPL 时,我不想使用配置文件中定义的值。

所以我改为将函数定义为除了常量之外的参数:

def connect_to_db(db_host, db_port_number, db_number):
    return get_connection(db_host, db_port_number, db_number)

然后当我的程序运行时,常量全部传递给我的main,它需要调用其他函数,然后调用这些函数并创建需要常量的类(可能都在不同的模块中):

def main(db_host, db_port_number, db_number, in_service, in_data):
    intermediate_function(
        db_host, db_port_number, db_number, other, parameters, here
    )
    other_intermediate_function(in_service, in_data, more, params, here)
    # etc...

def intermediate_function(db_host, db_port_number, db_number):
    # Other processing...
    c = connect_to_db(db_host, db_port_number, db_number)
    # Continued...

if __name__ == '__main__':
    main(DB_HOST, DB_PORT_NUMBER, DB_NUMBER, IN_SERVICE, IN_DATA)

问题在于,如果常量太多,这很快就会变得笨拙。如果我需要添加另一个常量,我有几个地方需要修改以确保我的代码不会中断。这是维护的噩梦。

什么是处理许多不同配置常量的正确 Pythonic 方式,以便代码仍然易于修改和易于测试?

【问题讨论】:

  • 亲爱的@user1436026,如果您认为我的解决方案是真正的帮助,请不要犹豫accept the answer!谢谢!

标签: python


【解决方案1】:

我的想法很简单,使用可选的关键字参数。 这是一个小例子,我在说什么:

# collect, the constants to a dictionary,
# with the proper key names
d = dict(a=1, b=2, c=3)

# create functions, where you want to use the constants
# and use the same key-names as in dictionary, and also
# add '**kwargs' at the end of the attribute list
def func1(a, b, d=4, **kwargs):
    print a, b, d

def func2(c, f=5, **kwargs):
    print c, f

# now any time, you use the original dictionary
# as an argument for one of these functions, this
# will let the function select only those keywords
# that are used later
func1(**d)
# 1 2 4
func2(**d)
# 3 5

这个想法允许您在原始字典中一次仅在一个地方修改常量列表。 所以这被移植回你的想法:

这是你的configuration.py:

# Your parsing, reading and storing functions goes here..

# Now create your dictionary
constants = dict(
    host  = DB_HOST,
    pnum  = DB_PORT_NUMBER,
    num   = DB_NUMBER,
    serv  = IN_SERVICE,
    data  = IN_DATA
)

这是你的other_file.py:

import configuration as cf

def main(host, pnum, num, serv, data, **kwargs):
    intermediate_function(
        host, pnum, num, 'other', 'params', 'here'
    )

def intermediate_function(host, pnum, num, *args):
    pass

# Now, you can call this function, with your
# dictionary as keyword arguments
if __name__ == '__main__':
    main(**cf.constants)

虽然这可行,但我不推荐此解决方案! 您的代码将更难维护,因为每次调用其中一个函数时,在传递常量字典的地方,您只会看到“一个”参数:字典本身,它并不那么冗长。所以我相信,你应该考虑一个更大的代码架构,在其中你使用更多的确定性函数(返回“真实”值),并使用它们相互链接,所以你没有一直传递所有这些常量。但这是我的看法:)

编辑:

如果上面提到的我的解决方案适合你,我也对如何存储和解析你的配置文件有更好的想法,并将其自动转换为字典。使用 JSON 而不是简单的.cfg 文件:

这将是你的conf.json:

{
    "host"  : "DB_HOST",
    "pnum"  : "DB_PORT_NUMBER",
    "num"   : "DB_NUMBER",
    "serv"  : "IN_SERVICE",
    "data"  : "IN_DATA"
}

您的configuration.py 将如下所示:

import json

with open('conf.json') as conf:
    # JSON parser will convert it to dictionary
    constants = json.load(conf)

【讨论】:

  • 现在我想起来了,你知道一个开源项目,它会从一个配置文件中获得许多参数,但确实使用更多确定性的函数,而不是传递常量?仍然只是试图提高我的代码质量。谢谢。
  • 首先我认为你必须确定你的目标到底是什么。如果您需要修复但可调整的用户输入,拥有一个配置文件可能会很好。另一个技巧是,要使用类(将您的代码构建为更面向对象的程序),它们可以随时存储和返回值,而无需一次又一次地调用方法。如果你想学习看看 sublime 文本编辑器的包,大部分时间他们也有一个配置文件(json)。
猜你喜欢
  • 1970-01-01
  • 2023-03-07
  • 1970-01-01
  • 2017-11-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-07-25
相关资源
最近更新 更多