2023.02.13 - [Python] - 결합과 추상화 - 2에 이어 가 보자.
의존성 주입과 가짜를 사용해 에지투에지 테스트
단위 테스트가 어느 정도 모이다 보면 시스템의 큰 덩어리를 한 번에 테스트하려고 할 것이다.
이때 엔트투엔드 테스트로 돌아갈 수도 있다. 하지만 엔트투엔드 테스트는 작성하거나 유지보수하기 어렵다. 대신 전체 시스템을 한 번에 테스트하되, 가짜 I/O를 사용하는 일종의 에지투에지 테스트를 자주 작성하자.
명시적 의존성:
def sync(source, dest, filesystem=FileSystem()): # 최상위 함수는 이제 reader와 filesystem이라는 두 가지 의존성을 노출한다.
source_hashes = filesystem.read(source) # reader를 호출해 파일이 있는 dict를 만든다.
dest_hashes = filesystem.read(dest)
for sha, filename in source_hashes.items():
if sha not in dest_hashes:
sourcepath = Path(source) / filename
destpath = Path(dest) / filename
filesystem.copy(sourcepath, destpath) # filesystem을 호출해 변경사항을 적용한다.
elif dest_hashes[sha] != filename:
olddestpath = Path(dest) / dest_hashes[sha]
newdestpath = Path(dest) / filename
filesystem.move(olddestpath, newdestpath)
for sha, filename in dest_hashes.items():
if sha not in source_hashes:
filesystem.delete(dest / filename)
DI를 사용하는 테스트
class FakeFilesystem: # 리스트를 이용한 테스트 더블
def __init__(self, path_hashes):
self.path_hashes = path_hashes
self.actions = []
def read(self, path):
return self.path_hashes[path]
def copy(self, src, dest):
self.actions.append(('COPY', src, dest))
def move(self, src, dest):
self.actions.append(('MOVE', src, dest))
def delete(self, dest):
self.actions.append(('DELETE', dest))
def test_when_a_file_exists_in_the_source_but_not_the_destination():
fakefs = FakeFilesystem({
'/src': {"hash1": "fn1"},
'/dst': {},
})
sync('/src', '/dst', filesystem=fakefs)
assert fakefs.actions == [("COPY", Path("/src/fn1"), Path("/dst/fn1"))]
def test_when_a_file_has_been_renamed_in_the_source():
fakefs = FakeFilesystem({
'/src': {"hash1": "fn1"},
'/dst': {"hash1": "fn2"},
})
sync('/src', '/dst', filesystem=fakefs)
assert fakefs.actions == [("MOVE", Path("/dst/fn2"), Path("/dst/fn1"))]
이 접근 방법의 장점은 테스트가 프로덕션 코드에 사용되는 함수와 완전히 같은 함수에 작용한다는 점이다. 단점은 상태가 있는 요소들을 명시적으로 표현해 전달하면서 작업을 수행해야 한다는 점이다. 이를 테스트가 야기한 설계 손상이라고 한다.
테스트 더블이란?
테스트를 진행하기 어려운 경우 이를 대신해 테스트를 진행할 수 있도록 만들어주는 객체
패치를 사용하지 않는 이유
책에서는 mock.patch를 쓰지 않는 이유를 기술한다. 모킹 프레임워크, 특히 멍키 패칭은 코드 냄새라고 칭한다. 이러한 mock.path를 쓰지 않는 대신에 코드베이스의 책임을 명확히 식별하고, 각 책임을 테스트 더블로 대치하기 쉬운 작은 객체에 집중한다. 테스트 더블을 쓰는 방식을 선호하는 이유는 다음과 같다.
- 사용 중인 의존성을 다른 코드로 패치하면 코드를 단위 테스트할 수는 있지만 설계를 개선하는 데는 아무 역할도 하지 못한다. mock.path를 사용하면 코드가 --dry-run 플래그에 대해 동작하지 않고 FTP 서버에 접속해 작동하지 못한다. 이런 경우를 처리하기 위해서는 추상화를 추가해야 한다.
- Mock을 사용한 테스트는 코드베이스의 구현 세부 사항에 더 밀접히 결합된다. Mock이 shutil.copy에 올바른 인수를 넘겼는가 등 여러 요소 사이의 상호작용을 검증하기 때문이다. 코드와 테스트 사이에 이런 식의 결합이 일어나면 경험상 테스트가 더 깨지기 쉬워지는 경향이 있다.
- Mock을 과용하면 테스트 스위트가 너무 복잡해져서 테스트 코드를 보고 테스트 대상 코드의 동작을 알아내기가 어려워진다.
'Python' 카테고리의 다른 글
Poweshell 환경에서 poetry 와 pyenv로 가상환경 세팅하기 (0) | 2023.03.15 |
---|---|
스택프레임, 빅오 (0) | 2023.03.01 |
결합과 추상화 - 2 (0) | 2023.02.13 |
Pytest - 3. Monkeypatching functions (0) | 2023.02.03 |
Pytest - 2. Fixture 알아보기 (0) | 2023.02.01 |