다른 프로젝트 (특히 subuser)에서 복사 한 다소 복잡한 (어쨌든) 기능 세트가 있습니다. 이 함수는 시스템에서 주어진 바이너리의 존재 여부 및 상태를 점검합니다. 그것들은 그대로 작동하지만 프로젝트에 적절한 테스트를하고 싶습니다. 나는 이것을 위해 python 3.4와 unittest.mock을 사용하고있다. 그래서 내 checks.py 모듈에는 다음과 같은 함수가 있습니다.모의를 사용하여 내 기능 테스트 전략
업데이트 : 최종 테스트 코드에서 함수 이름을 지정하는 데 몇 가지 스타일 항목이 변경되었습니다 (아래 참조).
import os
def is_executable(fpath):
'''
Returns true if the given filepath points to an executable file.
'''
return os.path.isfile(fpath) and os.access(fpath, os.X_OK)
# Origonally taken from: http://stackoverflow.com/questions/377017/test-if-executable-exists-in-python
def query_path(test):
'''
Search the PATH for an executable.
Given a function which takes an absolute filepath and returns True when the
filepath matches the query, return a list of full paths to matched files.
'''
matches = []
def append_if_matches(exeFile):
if is_executable(exeFile):
if test(exeFile):
matches.append(exeFile)
for path in os.environ['PATH'].split(os.pathsep):
path = path.strip('"')
if os.path.exists(path):
for fileInPath in os.listdir(path):
exeFile = os.path.join(path, fileInPath)
append_if_matches(exeFile)
return matches
def which(program):
'''
Check for existence and executable state of program.
'''
fpath, fname = os.path.split(program)
if not fpath == '':
if is_executable(program):
return program
else:
def matches_program(path):
fpath, fname = os.path.split(path)
return program == fname
programMatches = query_path(matches_program)
if len(programMatches) > 0:
return programMatches[0]
return None
이러한 것들은 훌륭하게 수행되며, PATH에 바이너리가 있는지 확인하고 실행 파일인지 확인한 후 첫 번째 결과를 반환합니다. 기본적으로 리눅스 'which'명령을 재생성합니다.
내 테스트 모듈은 지금까지 다음과 같습니다
이참고 : 변명 다른 기능 이름 스타일, 최종 결과에 업데이트, 아래를 참조하십시오.
import unittest
import unittest.mock as mock
from myproject import checks
class TestSystemChecks(unittest.TestCase):
def setUp(self):
pass
def tearDown(self):
pass
# This test works great
@mock.patch('os.path.isfile')
@mock.patch('os.access')
def test_isExecutable(self, mock_isfile, mock_access):
# case 1
mock_isfile.return_value = True
mock_access.return_value = True
self.assertTrue(
checks.isExecutable('/some/executable/file'))
# case 2
mock_isfile.return_value = True
mock_access.return_value = False
self.assertFalse(
checks.isExecutable('/some/non/executable/file'))
# THIS ONE IS A MESS.
@mock.patch('os.path.isfile')
@mock.patch('os.path.exists')
@mock.patch('os.access')
@mock.patch('os.listdir')
@mock.patch('os.environ')
def test_queryPATH(
self, mock_isfile, mock_access, mock_environ, mock_exists,
mock_listdir):
# case 1
mock_isfile.return_value = True
mock_access.return_value = True
mock_exists.return_value = True
mock_listdir.return_value = [
'somebin',
'another_bin',
'docker']
mock_environ.dict['PATH'] = \
'/wrong:' +\
'/wrong/path/two:' +\
'/docker/path/one:' +\
'/other/docker/path'
target_paths = [
'/docker/path/one/docker',
'/other/docker/path/docker']
def isPathToDockerCommand(path):
return True
self.assertEqual(
target_paths,
checks.queryPATH(isPathToDockerCommand))
def test_which(self):
pass
그래서 queryPATH()에 대한 테스트가 여기 내 질문입니다. 한 가지 기능을 너무 많이 사용하려고합니까? 매번 이러한 모든 모의 객체를 다시 만들거나 이러한 모든 테스트를 위해 setUp()에서 메타 객체 (또는 객체 세트)를 설정하는 방법이 있습니까? 아니면 원래 코드가 어떻게 작동하는지 이해하지 못하고 테스트를 올바로 설정하지 않았을 수도 있습니다. (모의 객체 사용은 정확합니다.) 이 테스트 수율을 실행 한 결과, 때문에 테스트의 복잡성과 함수 자체의
checks.queryPATH(isPathToDockerCommand))
AssertionError: Lists differ: ['/docker/path/one/docker', '/other/docker/path/docker'] != []
First list contains 2 additional elements.
First extra element 0:
/docker/path/one/docker
- ['/docker/path/one/docker', '/other/docker/path/docker']
+ []
, 나는 내 테스트 권리를 디자인 할 수없는 이유를 모르겠어요. 이것은 내 단위 테스트에서 광범위하게 모의 작업을 사용하는 첫 번째 작업이며, 프로젝트 진행을 시작하기 전에 바로 가져 와서 TDD 스타일을 코딩 할 수 있습니다. 감사!
UPDATE : 여기
해결하고자 내 최종 결과는 모두이 세 가지 기능 영광에서처럼 보이는 결국 것입니다. @robjohncox 점에
import unittest
import unittest.mock as mock
from myproject import checks
class TestSystemChecks(unittest.TestCase):
def setUp(self):
pass
def tearDown(self):
pass
@mock.patch('os.access')
@mock.patch('os.path.isfile')
def test_is_executable(self,
mock_isfile,
mock_access):
# case 1
mock_isfile.return_value = True
mock_access.return_value = True
self.assertTrue(
checks.is_executable('/some/executable/file'))
# case 2
mock_isfile.return_value = True
mock_access.return_value = False
self.assertFalse(
checks.is_executable('/some/non/executable/file'))
@mock.patch('os.listdir')
@mock.patch('os.access')
@mock.patch('os.path.exists')
@mock.patch('os.path.isfile')
def test_query_path(self,
mock_isfile,
mock_exists,
mock_access,
mock_listdir):
# case 1
# assume file exists, and is in all paths supplied
mock_isfile.return_value = True
mock_access.return_value = True
mock_exists.return_value = True
mock_listdir.return_value = ['docker']
fake_path = '/path/one:' +\
'/path/two'
def is_path_to_docker_command(path):
return True
with mock.patch.dict('os.environ', {'PATH': fake_path}):
self.assertEqual(
['/path/one/docker', '/path/two/docker'],
checks.query_path(is_path_to_docker_command))
# case 2
# assume file exists, but not in any paths
mock_isfile.return_value = True
mock_access.return_value = True
mock_exists.return_value = False
mock_listdir.return_value = ['docker']
fake_path = '/path/one:' +\
'/path/two'
def is_path_to_docker_command(path):
return True
with mock.patch.dict('os.environ', {'PATH': fake_path}):
self.assertEqual(
[],
checks.query_path(is_path_to_docker_command))
# case 3
# assume file does not exist
mock_isfile.return_value = False
mock_access.return_value = False
mock_exists.return_value = False
mock_listdir.return_value = ['']
fake_path = '/path/one:' +\
'/path/two'
def is_path_to_docker_command(path):
return True
with mock.patch.dict('os.environ', {'PATH': fake_path}):
self.assertEqual(
[],
checks.query_path(is_path_to_docker_command))
@mock.patch('os.listdir')
@mock.patch('os.access')
@mock.patch('os.path.exists')
@mock.patch('os.path.isfile')
def test_which(self,
mock_isfile,
mock_exists,
mock_access,
mock_listdir):
# case 1
# file exists, only take first result
mock_isfile.return_value = True
mock_access.return_value = True
mock_exists.return_value = True
mock_listdir.return_value = ['docker']
fake_path = '/path/one:' +\
'/path/two'
with mock.patch.dict('os.environ', {'PATH': fake_path}):
self.assertEqual(
'/path/one/docker',
checks.which('docker'))
# case 2
# file does not exist
mock_isfile.return_value = True
mock_access.return_value = True
mock_exists.return_value = False
mock_listdir.return_value = ['']
fake_path = '/path/one:' +\
'/path/two'
with mock.patch.dict('os.environ', {'PATH': fake_path}):
self.assertEqual(
None,
checks.which('docker'))
댓글 : 그는 그의 대답에 명시된 바와 같이
- 주문 또는 장식 사항.
- 데코레이터를 사용하여 사전
patch.dict
을 패치하는 것은 이상하게도 다른 데코레이터처럼 인수로 함수에 객체를 전달할 필요가 없습니다. 소스 또는 뭔가에서 dict를 수정해야합니다. - 테스트를 위해 장식 자 대신 컨텍스트를 변경하는 방법을 사용하여 다른 경로를 사용하여 다른 사례를 쉽게 테스트 할 수있었습니다.
이것은 실제로 도움이됩니다. 저는 장식 자의 순서가 중요하다는 것을 깨닫지 못했습니다. 나는 지금 훨씬 더 가깝다. 그러나 나는 전에 반대 한 적이 하나의 새로운 문제가 있습니다. 'mock.patch.dict'를 데코레이터로 패치하려고 할 때, 필자 함수는 항상 충분한 위치 인수를 불평합니다 :'TypeError : test_queryPATH() missing 1 필요한 위치 인자 : 'mock_environ''. 튜토리얼에서이 예제를 보았을 때, 모든 사람들은 항상 dicts에 대해 "with"컨텍스트 관리자를 사용합니다. 데코레이터가 내 함수를 arg로 보내지 않는 이유는 무엇입니까? – brianclements
'test_queryPATH'의 서명에서'mock_environ' 매개 변수를 제거해야한다고 생각합니다. 사전을 조롱 할 때 프레임 워크가 모의 객체를 테스트에 전달하지 않는 것처럼 보입니다. – robjohncox
기록을 보면, 당신이 맞았다 고 생각합니다. 'patch.dict'를 데코레이터로 패치하는 것은 어떤 객체를 함수로 전달할 필요가 없습니다. 결국 실제로 "with"컨텍스트 관리자를 사용하여 결국 동일한 테스트에서 다른 경로로 여러 사례를 테스트 할 수 있지만 결국 도움이되었습니다. 나중에이 질문에 대한 참조를 위해 최종 코드를 업데이트 할 것입니다. – brianclements