2011-03-02 110 views
3

我正在寫一個功能測試的遺留Python腳本,這樣我可以做一個行修改時沒有被恐懼癱瘓。 )攔截subprocess.Popen調用在Python

在考慮中的腳本調用的wget使用subprocess.Popen下載一個XML文件,然後將其解析(1):

def download_files(): 
    os.mkdir(FEED_DIR) 
    os.chdir(FEED_DIR) 

    wget_process = Popen(
     ["wget", "--quiet", "--output-document", "-", "ftp://foo.com/bar.tar"], 
     stdout=PIPE 
    ) 
    tar_process = Popen(["tar", "xf", "-"], stdin=wget_process.stdout) 
    stdout, stderr = tar_process.communicate() 

顯然,這將是優選的修改腳本使用一個HTTP庫,而不是EXEC-ING wget的,但正如我所說,這是一個傳統的劇本,所以我需要讓我的變化很小,絕對專注於業務需求,它有沒有關係是如何獲得的XML文件。

顯而易見的解決方案我是呼叫截取到subprocess.Popen,回到我自己的測試XML。 Intercept method calls in Python演示瞭如何使用SETATTR要做到這一點,但我必須失去了一些東西:

Python 2.6.6 (r266:84292, Sep 15 2010, 16:22:56) 
[GCC 4.4.5] on linux2 
Type "help", "copyright", "credits" or "license" for more information. 
>>> import subprocess 
>>> object.__getattribute__(subprocess, 'Popen') 
<class 'subprocess.Popen'> 
>>> attr = object.__getattribute__(subprocess, 'Popen') 
>>> hasattr(attr, '__call__') 
True 
>>> def foo(): print('foo') 
... 
>>> foo 
<function foo at 0x7f8e3ced3c08> 
>>> foo() 
foo 
>>> setattr(subprocess, '__call__', foo) 
>>> getattr(subprocess, '__call__') 
<function foo at 0x7f8e3ced3c08> 
>>> subprocess.Popen([ r"tail", "-n 1", "x.txt" ], stdout = subprocess.PIPE) 
<subprocess.Popen object at 0x7f8e3ced9cd0> 
>>> tail: cannot open `x.txt' for reading: No such file or directory 

正如你所看到的,真正的subprocess.Popen被調用,儘管被正確設置屬性(在至少我的未經訓練的眼睛)。這是剛剛在交互式Python運行這個的結果,或者我應該期待相同的結果掉落這種代碼在我的測試腳本:

class MockProcess: 
    def __init__(self, output): 
    self.output = output 

    def stderr(): pass 
    def stdout(): return self.output 

    def communicate(): 
    return stdout, stderr 


# Runs script, returning output 
# 
def run_agent(): 
    real_popen = getattr(subprocess.Popen, '__call__') 
    try: 
    setattr(subprocess.Popen, '__call__', lambda *ignored: MockProcess('<foo bar="baz" />') 
    ) 
    return real_popen(['myscript.py'], stdout = subprocess.PIPE).communicate()[0] 
    finally: 
    setattr(subprocess.Popen, '__call__', real_popen) 

回答

3

幾個問題,我的方法:

我沒有意識到,參數表是神奇的Python,也不是說我需要kwargs也是如此。

我更換subprocess.Popen.__call__,當我要更換subprocess.Popen本身。

最重要的是,替換Popen顯然只會影響當前進程,而不是我的代碼想要爲腳本執行的新進程。新的run_agent方法應該如下所示:

def run_agent(): 
    real_popen = getattr(subprocess, 'Popen') 
    try: 
    setattr(subprocess, 'Popen', lambda *args, **kwargs: MockProcess('<foo bar="baz" />') 
    imp.load_module(
     MY_SCRIPT.replace('.py', '').replace('.', '_'), 
     file(SCRIPT_DIR), 
     MY_SCRIPT, 
     ('.py', 'r', imp.PY_SOURCE) 
    ) 
    finally: 
    setattr(subprocess.Popen, '__call__', real_popen) 

我的交互式會話中存在拼寫錯誤。它應該是:

Python 2.6.6 (r266:84292, Sep 15 2010, 16:22:56) 
[GCC 4.4.5] on linux2 
Type "help", "copyright", "credits" or "license" for more information. 
>>> import subprocess 
>>> setattr(subprocess, 'Popen', lambda *args, **kwargs: [1,2]) 
>>> subprocess.Popen([1], stdout=1) 
[1, 2] 
1

你是不是在你的測試腳本設置subprocess.__call__,而不是subprocess.Popen.__call__那是失敗的?

+0

是的,那是我的一個問題。我還發現了另外兩個,這要感謝一位同事。 : -/ – 2011-03-02 11:43:42

+0

嘿,如果它現在有效,你應該是:-),而不是: - /。 ;-)順便說一句,你可以用你發現的工作回答你自己的問題! – DSM 2011-03-02 11:47:49

+0

是的,我現在:)看到下面我的自我答案。 – 2011-03-02 12:02:14

3

當然,Python版本的FlexMock是更好的選擇!

import subprocess 
from cStringIO import StringIO 
from flexmock import flexmock 

def run_agent(): 
    flexmock(subprocess).should_receive('Popen').and_return(
     StringIO(''), StringIO('<foo bar="baz" />') 
)