果然还是把复杂问题简单化比较爽啊
Python September 16th, 2009
事情的起因是这样子的,小员在写一个测试工具,主要是管理 Xvfb 和 Selenium Server,启动多个 Server 来并行跑多个测试用例,出错的时候截图生成报告。
然后开发人员的测试代码是这么写的:
1 2 | def setUP(self): self.selenium = selenium(host, port, "*firefox", "http://xxx.yyy.zzz:1234/") |
这里的 host 和 port 在测试用例里是写死的,而在这个测试工具中,由于启动的 Selenium Server 的端口是随机绑定的,所以需要自动的把测试用例里的连接修改成对应的参数。
我俩讨论了下,决定用 decorator 来改掉 selenium 的构造函数,然后有了以下关键部分的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | def change_selenium_params(host, port, browser='', baseURL=''): def inner(func): def wrapper(*kargs, **kwargs): kargs = list(kargs) kargs[1:3] = host, port return func(*kargs, **kwargs) return wrapper return inner @stop_servers def run(self): """ Run testcases """ self.xvfb.start() self.selenium_server = serverctl.SeleniumServer(self.xvfb.display) self.selenium_server.start() for testcase_file in self.testcases_status['waiting']: selenium.selenium._init_ = change_selenium_params('localhost', self.selenium_server.port)(selenium.selenium.__init__) # Make testsuite and run it here ...... self.selenium_server.restart() self.selenium_server.stop() self.xvfb.stop() |
由于 Selenium 关闭 Firefox 有 Bug,所以不得不每跑一个 testsuite 就重启一次 Selenium Server,这时候发现 testsuite 用的还是第一次传过去的端口。结果看起来奇怪,这个问题还和其它混在一起了,而且这玩意比较难跟踪,调试器也不好用,于是乎,我们写了个简化版的原型来分析,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | class Selenium: def __init__(self, port): print 'Port:', port def change_port(port): def inner(func): def wrapper(*kargs, **kwargs): kargs = list(kargs) kargs[1] = port return func(*kargs, **kwargs) return wrapper return inner for i in xrange(3): Selenium.__init__ = change_port(i)(Selenium.__init__) a = Selenium(9) |
这时候的输出是:
1 2 3 | Port: 0 Port: 0 Port: 0 |
然后在 decorator 里加了条输出语句:
1 2 3 4 5 6 7 8 9 | def change_port(port): def inner(func): def wrapper(*kargs, **kwargs): kargs = list(kargs) print 'Change port:', port # Add a debug output. kargs[1] = port return func(*kargs, **kwargs) return wrapper return inner |
这时候的输出是:
1 2 3 4 5 6 7 8 9 | Change port: 0 Port: 0 Change port: 1 Change port: 0 Port: 0 Change port: 2 Change port: 1 Change port: 0 Port: 0 |
这下终于知道问题所在了,可怜的 A.__init__ 函数每次执行都多被套一层 decorator,到了最后就变成了:
1 | change_port(2)(change_port(1)(change_port(0)(A.__init__))) |
嗯,用个临时变量先把初始的 A.__init__ 保存起来就 OK 了,比如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | class A: def __init__(self, port): print 'Port:', port orig_func = A.__init__ def change_port(port): def inner(func): def wrapper(*kargs, **kwargs): kargs = list(kargs) print 'Change port:', port # Add a debug output. kargs[1] = port return func(*kargs, **kwargs) return wrapper return inner for i in xrange(3): A.__init__ = orig_func A.__init__ = change_port(i)(A.__init__) a = A(9) |
Tags: Python