Python 스타일 가이드
GitLab v19.1GitLab에서의 테스트는 Python 코드베이스를 포함하여 사후에 추가되는 것이 아닌 핵심 우선순위입니다. Python 테스트에는 Pytest를 사용하세요. Five Factor Testing: 테스트가 필요한 이유는 무엇인가?
테스트#
개요#
GitLab에서의 테스트는 Python 코드베이스를 포함하여 사후에 추가되는 것이 아닌 핵심 우선순위입니다. 따라서 기능 설계와 함께 처음부터 테스트 설계 품질을 고려하는 것이 중요합니다.
Python 테스트에는 Pytest를 사용하세요.
권장 읽기 자료#
-
Five Factor Testing: 테스트가 필요한 이유는 무엇인가?
-
Principles of Automated Testing: 테스트 수준. 테스트 우선순위. 테스트 비용.
테스트 수준#
테스트를 작성하기 전에 다양한 테스트 수준을 이해하고 변경 사항에 적합한 수준을 결정하세요.
다양한 테스트 수준과 변경 사항을 어느 수준에서 테스트해야 하는지 결정하는 방법에 대해 자세히 알아보세요.
권장 사항#
테스트 파일 이름을 테스트 대상 파일과 동일하게 지정하기#
단위 테스트의 경우, 테스트 파일을 test_{테스트할_파일명}.py로 명명하고 동일한 디렉터리 구조에 배치하면 나중에 테스트를 찾기가 쉬워집니다. 또한 이름은 같지만 모듈이 다른 파일 간의 혼동을 방지할 수 있습니다.
File: /foo/bar/cool_feature.py
# Bad
Test file: /tests/my_cool_feature.py
# Good
Test file: /tests/foo/bar/test_cool_feature.py
NamedTuple을 사용하여 파라미터화 테스트 케이스 정의하기#
Pytest 파라미터화 테스트는 코드 반복을 효과적으로 줄여주지만, Ruby의 더 읽기 쉬운 문법과 달리 테스트 케이스 정의에 튜플을 사용합니다. 파라미터나 테스트 케이스가 많아질수록 이 튜플 기반 테스트는 이해하고 유지 관리하기 어려워집니다.
대신 Python의 NamedTuple을 사용하면 다음과 같은 이점이 있습니다:
-
이름이 지정된 필드로 더 명확한 구성을 강제할 수 있습니다.
-
테스트를 더 자체 문서화하기 쉽게 만들 수 있습니다.
-
파라미터의 기본값을 쉽게 정의할 수 있습니다.
-
복잡한 테스트 시나리오에서 가독성을 향상시킬 수 있습니다.
# Good: Short examples, with small numbers of arguments. Easy to map what each value maps to each argument
@pytest.mark.parametrize(
(
"argument1",
"argument2",
"expected_result",
),
[
# description of case 1,
("value1", "value2", 200),
# description of case 2,
...,
],
)
def test_get_product_price(argument1, argument2, expected_result):
assert get_product_price(value1, value2) == expected_cost
# Bad: difficult to map a value to an argument, and to add or remove arguments when updating test cases
@pytest.mark.parametrize(
(
"argument1",
"argument2",
"argument3",
"expected_response",
),
[
# Test case 1:
(
"value1",
{
...
},
...
),
# Test case 2:
...
]
)
def test_my_function(argument1, argument2, argument3, expected_response):
...
# Good: NamedTuples improve readability for larger test scenarios.
from typing import NamedTuple
class TestMyFunction:
class Case(NamedTuple):
argument1: str
argument2: int = 3
argument3: dict
expected_response: int
TEST_CASE_1 = Case(
argument1="my argument",
argument3={
"key": "value"
},
expected_response=2
)
TEST_CASE_2 = Case(
...
)
@pytest.mark.parametrize(
"test_case", [TEST_CASE_1, TEST_CASE_2]
)
def test_my_function(test_case):
assert my_function(case.argument1, case.argument2, case.argument3) == case.expected_response
모킹#
-
unittest.mock라이브러리를 사용하세요. -
예를 들어 메서드 호출 경계와 같이 적절한 수준에서 모킹하세요.
-
외부 서비스와 API를 모킹하세요.
코드 스타일#
코드 품질과 보안을 보장하기 위해 자동화된 도구를 사용하는 것을 권장합니다. 로컬에서뿐만 아니라 CI 파이프라인에서도 다음 도구들을 실행하는 것을 고려하세요:
포맷팅 도구#
-
Black: 일관된 스타일을 강제하는 코드 포맷터
-
isort: import 문을 정렬하고 구성
린팅 도구#
-
flake8: PEP-8 준수 여부 및 일반적인 오류 검사
-
pylint: 코드 품질을 위한 더 포괄적인 린터
-
mypy: Python용 정적 타입 검사기
테스트 도구#
- pytest: 커버리지 리포팅이 포함된 테스트 프레임워크
보안 도구#
-
종속성 스캐닝: 의존성의 취약점 검사
-
시크릿 탐지: 리포지터리에 시크릿이 커밋되지 않도록 보장
-
SAST (semgrep): 정적 애플리케이션 보안 테스트