2011-03-02 3 views
3

레거시 파이썬 스크립트에 대한 기능 테스트를 작성하여 두려움에 의해 마비되지 않고 한 줄로 변경할 수 있습니다. ;)subprocess.Popen call in Python

문제의 스크립트는 다음 (1) 구문 분석하는 XML 파일 다운로드 subprocess.Popen를 사용하여 wget을 호출 : 분명히

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() 

, 사용하는 스크립트를 수정하는 것이 바람직 할 것 wget을 실행하는 대신 HTTP 라이브러리를 사용하지만, 내가 말했던 것처럼 이것은 레거시 스크립트이므로 XML 파일을 얻는 방법과는 아무런 관련이없는 비즈니스 요구 사항에 최소한으로 초점을 맞추어야합니다.

확실한 해결책은 하위 프로세스에 대한 호출을 가로 채고,을 호출하고 내 자신의 테스트 XML을 반환하는 것입니다. 에서 (올바르게 설정되는 속성에도 불구하고, 당신이 볼 수 있듯이

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가 호출되고 : Intercept method calls in Python이 작업을 수행 할 않은 setattr를 사용하는,하지만 난 뭔가 빠진해야하는 방법을 보여줍니다 적어도 내 숙련되지 않은 눈까지). 대화 형 파이썬이 실행이 단지 결과인가, 아니면 내 테스트 스크립트에 코드 이런 종류의 낙하 같은 결과를 기대한다 : 당신이 당신의 테스트 스크립트 대신 subprocess.Popen.__call__subprocess.__call__을 설정하지 않는

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

몇 가지 문제 :

내가 인수 파이썬에 마법 실현,도 나뿐만 아니라 kwargs로 필요하다고하지 않았다.

subprocess.Popen을 교체해야 할 때 subprocess.Popen.__call__을 교체하고있었습니다.

가장 중요한 점은 분명히 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

있습니까 그게 실패한거야? 나의 접근 방식

+0

그래, 그게 내 문제 중 하나 였어. 동료 덕분에 두 명을 더 찾았습니다. : -/ –

+0

이봐 요, 지금 작동한다면, 당신은 :-), 안됩니다 : - /. ;-) BTW, 당신은 당신이 일하는 것으로 당신 자신의 질문에 대답 할 수 있습니다! – DSM

+0

그래, 나 지금 :) 내 자아를 위해 아래를 보아라. –

3

물론 FlexMock의 Python 버전은 더 나은 선택입니다!

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" />') 
)