19. 가상환경·코드 스타일·테스트
- 가상환경이 왜 필요한지 이해하고
venv·pip로 다룬다. - 2026년 현대 도구(uv·Ruff)의 흐름을 안다.
- PEP 8 코드 스타일과 포매터의 역할을 이해한다.
- 테스트를 작성하는 법(assert·pytest)을 익힌다.
들어가며: 코드를 넘어 "프로젝트"로
지금까지는 코드 자체를 배웠습니다. 이 장은 조금 다릅니다. 여러 사람이, 여러 프로젝트를, 오래 유지보수할 때 필요한 도구와 습관을 다룹니다. 실무로 가는 다리라고 보면 됩니다.
📎 이 장의 도구 생태계는 빠르게 변하는 영역입니다. 그래서 왜 그게 필요한가라는 개념을 먼저 표준 도구로 익히고, 그 위에 2026년의 현대적 도구를 얹는 순서로 설명합니다. 도구 이름은 바뀌어도 그 아래 개념(격리·일관성·검증)은 오래갑니다.
가상환경: 프로젝트별 격리 공간
왜 필요한가
프로젝트 A는 어떤 라이브러리의 1.0 버전이 필요하고, 프로젝트 B는 같은 라이브러리의 2.0 버전이 필요하다고 합시다. 컴퓨터 전체에 라이브러리를 하나만 깔 수 있다면, 둘 중 하나는 망가집니다.
가상환경(virtual environment)은 이 문제를 해결합니다. 프로젝트마다 독립된 Python 환경과 라이브러리 폴더를 만들어, 서로 간섭하지 않게 격리합니다. 프로젝트 A의 환경과 B의 환경은 완전히 별개라, 각자 필요한 버전을 자유롭게 둘 수 있습니다.
flowchart TD
Sys["💻 시스템 Python<br/>(깨끗하게 유지)"]:::data
Sys --> A["📦 프로젝트 A 환경<br/>requests 1.0"]:::proc
Sys --> B["📦 프로젝트 B 환경<br/>requests 2.0"]:::proc
Sys --> C["📦 프로젝트 C 환경<br/>django 5.0"]:::result
classDef data fill:#a8dadc,stroke:#457b9d,color:#1d3557
classDef proc fill:#7fd8d8,stroke:#2a9d8f,color:#14532d
classDef result fill:#b8e6c1,stroke:#34a853,color:#14532d
표준 도구: venv + pip
venv는 Python에 기본 포함된 가상환경 도구입니다. 터미널에서 다음과 같이 씁니다.
# 1) 가상환경 만들기 (.venv 라는 폴더가 생김) python -m venv .venv # 2) 활성화 (셸 프롬프트 앞에 (.venv)가 붙음) # macOS / Linux: source .venv/bin/activate # Windows: .venv\Scripts\activate # 3) 이제 pip로 설치하면 이 환경에만 설치됨 pip install requests # 4) 끝나면 비활성화 deactivate
활성화하면 셸 프롬프트 앞에 (.venv)가 표시되어, 지금 가상환경 안에 있음을 알려줍니다. 이 상태에서 pip install하면 시스템이 아니라 이 환경에만 설치됩니다.
pip는 Python의 패키지 설치 도구입니다(초급편에서 잠깐 언급). 자주 쓰는 명령은 이렇습니다.
| 명령 | 하는 일 |
|---|---|
pip install 패키지 | 패키지 설치 |
pip install 패키지==2.0 | 특정 버전 설치 |
pip list | 설치된 패키지 목록 |
pip freeze > requirements.txt | 의존성 목록을 파일로 저장 |
pip install -r requirements.txt | 파일의 의존성을 한 번에 설치 |
requirements.txt는 "이 프로젝트가 필요로 하는 라이브러리 목록"입니다. 이 파일을 함께 공유하면, 다른 사람이 pip install -r requirements.txt 한 번으로 똑같은 환경을 재현할 수 있습니다.2026년의 흐름: uv
최근 Python 생태계에는 큰 변화가 있습니다. uv라는 도구가 pip·venv를 비롯한 여러 도구를 하나로 통합하며 빠르게 표준이 되고 있습니다. uv는 가상환경 생성·패키지 설치·버전 관리를 한 도구로 처리하고, 속도가 수십~수백 배 빠릅니다.
# uv로 새 프로젝트 시작 (가상환경·설정 파일을 자동 생성) uv init my-project cd my-project # 의존성 추가 (가상환경 자동 관리) uv add requests # 코드 실행 (가상환경을 알아서 찾아 실행) uv run python main.py
📎 입문 단계에서는 개념(가상환경이 왜 필요한가)을 venv로 이해하는 것이 우선입니다. uv는 "그 개념을 더 빠르고 편하게 자동화한 현대 도구"로 알아두고, 실무에 들어갈 때 도입하면 됩니다. 둘 다 "프로젝트를 격리한다"는 같은 일을 합니다. 최신 사용법은 공식 문서에서 확인하세요. (도구 생태계는 계속 변하므로, 이 책의 설명도 시점에 따라 달라질 수 있습니다.)
코드 스타일: PEP 8과 포매터
PEP 8 다시 보기
초급편에서 PEP 8(Python Enhancement Proposal 8, 공식 코드 스타일 가이드)을 잠깐 봤습니다. snake_case, 공백 4칸 들여쓰기 등을 정한 약속이죠. PEP 8의 목적은 단 하나, 읽기 쉬운 코드입니다. 코드는 작성되는 횟수보다 읽히는 횟수가 훨씬 많으니까요.
몇 가지 대표 규칙을 봅시다.
# ❌ PEP 8 위반 def calc( x,y ): z=x+y return z # ✅ PEP 8 준수 def calc(x, y): z = x + y return z
| 항목 | 규칙 |
|---|---|
| 들여쓰기 | 공백 4칸 (탭 아님) |
| 연산자 주변 | a + b (양쪽 공백 1칸) |
| 쉼표 뒤 | f(a, b) (뒤에 공백) |
| 변수·함수명 | snake_case |
| 클래스명 | PascalCase |
| 줄 길이 | 너무 길지 않게 (대략 79~100자) |
포매터: 스타일을 자동으로
PEP 8을 사람이 일일이 지키는 건 비효율적입니다. 포매터(formatter)가 코드를 자동으로 규칙에 맞게 정리해줍니다. 저장할 때마다 코드가 가지런해지죠.
2026년 현재 표준으로 떠오른 도구는 Ruff입니다. Ruff는 포매팅(코드 정렬)과 린팅(문제 검사)을 한 번에, 매우 빠르게 처리합니다.
# 코드 검사 (문제점 찾기) ruff check . # 자동 수정 가능한 문제는 고치기 ruff check --fix . # 코드 포매팅 (스타일 정리) ruff format .
black, 검사에 flake8, import 정렬에 isort 등 여러 도구를 따로 썼습니다. Ruff는 이들을 하나로 합쳤습니다. 핵심은 도구 이름이 아니라 "스타일은 사람이 손으로 맞추지 말고 도구에 맡긴다"는 습관입니다.테스트: 코드가 맞는지 자동 검증
왜 테스트하는가
함수를 고쳤는데 다른 곳이 망가지지 않았을까? 새 기능이 기존 기능을 깨뜨리지 않았을까? 테스트(test)는 이를 자동으로 확인하는 코드입니다. 한 번 짜두면, 코드를 바꿀 때마다 "여전히 잘 동작하는가"를 즉시 검증할 수 있습니다.
assert로 시작하기
테스트의 핵심은 assert 문입니다. "이게 참이어야 한다"고 주장하고, 거짓이면 에러를 냅니다.
assert 2 + 2 == 4 # 참 → 아무 일 없음 assert 2 + 2 == 5 # 거짓 → AssertionError 발생!
테스트할 함수가 있다고 합시다.
# calculator.py def add(a, b): return a + b def divide(a, b): if b == 0: raise ValueError("0으로 나눌 수 없습니다") return a / b
assert로 직접 검증할 수 있습니다.
from calculator import add, divide assert add(2, 3) == 5 assert add(-1, 1) == 0 assert divide(10, 2) == 5.0 print("모든 테스트 통과!")
⭐ pytest: 테스트의 표준 도구
assert를 직접 실행하는 건 한계가 있습니다. 어느 테스트가 실패했는지, 몇 개가 통과했는지 정리해주는 도구가 필요하죠. pytest가 2026년 현재 사실상 표준입니다.
pytest는 test_로 시작하는 함수를 자동으로 찾아 실행합니다. 함수 안에서 그냥 assert를 쓰면 됩니다.
# test_calculator.py from calculator import add, divide, is_even import pytest def test_add(): assert add(2, 3) == 5 assert add(-1, 1) == 0 def test_divide(): assert divide(10, 2) == 5.0 def test_divide_by_zero(): with pytest.raises(ValueError): # 이 블록에서 ValueError가 나야 통과 divide(10, 0)
터미널에서 실행하면 결과를 깔끔하게 보여줍니다.
$ pytest test_calculator.py -v test_calculator.py::test_add PASSED test_calculator.py::test_divide PASSED test_calculator.py::test_divide_by_zero PASSED ===== 3 passed in 0.01s =====
pytest.raises는 "이 코드가 특정 예외를 일으켜야 한다"를 검증합니다. 0으로 나눌 때 ValueError가 제대로 나는지 확인하는 것이죠.
| pytest 요소 | 의미 |
|---|---|
test_로 시작하는 함수 | pytest가 자동으로 찾아 실행 |
assert 조건 | 조건이 거짓이면 그 테스트 실패 |
with pytest.raises(예외): | 그 블록에서 예외가 나야 통과 |
pytest -v | 자세히(verbose) 결과 표시 |
unittest라는 테스트 도구도 내장돼 있습니다. 하지만 새 프로젝트에서는 더 간결한 pytest가 널리 쓰입니다. 핵심 습관은 "중요한 함수에는 테스트를 짠다"는 것입니다.assert가 기본이고, pytest가 표준 도구다. 테스트가 있으면 코드를 고칠 때마다 "안 망가졌나"를 즉시 확인할 수 있어 안심하고 수정한다.현대 도구 흐름 정리
2026년 Python 프로젝트의 전형적인 작업 흐름을 도식으로 보면 이렇습니다(개념 이해용으로, 세부는 변할 수 있습니다).
flowchart LR
Setup["프로젝트 시작<br/>가상환경 (venv·uv)"]:::data --> Code["코드 작성"]:::proc
Code --> Style["스타일 정리<br/>(Ruff)"]:::proc
Style --> Test["테스트 실행<br/>(pytest)"]:::proc
Test --> Done["안심하고 배포·공유 ✅"]:::result
classDef data fill:#a8dadc,stroke:#457b9d,color:#1d3557
classDef proc fill:#7fd8d8,stroke:#2a9d8f,color:#14532d
classDef result fill:#b8e6c1,stroke:#34a853,color:#14532d
이 장에서 배운 것
- 가상환경(venv)은 프로젝트별 격리 공간으로, 라이브러리 충돌을 막는다.
pip으로 패키지를 설치하고requirements.txt로 환경을 공유한다. - 2026년에는 uv가 venv·pip를 통합한 빠른 도구로 떠오르고 있다. 개념은 venv로 익히고 uv로 자동화한다.
- PEP 8은 읽기 쉬운 코드를 위한 스타일 약속이고, Ruff 같은 포매터가 이를 자동 적용한다.
- 테스트는 코드가 맞는지 자동 검증한다.
assert가 기본, pytest가 표준이며,pytest.raises로 예외도 검증한다. - 도구 이름은 변해도 개념(격리·일관된 스타일·자동 검증)은 오래간다.
🧪 실습 문제
문제 1. 가상환경이 필요한 이유를 한 문장으로 설명하세요.
문제 2. 다음 명령들이 각각 하는 일을 적으세요.
python -m venv .venv pip install -r requirements.txt
문제 3. 다음 코드를 PEP 8에 맞게 고치세요.
def Add(x,y): Result=x+y return Result
문제 4. 함수 def square(n): return n * n에 대한 pytest 테스트 함수 test_square를 작성하세요. (square(3) == 9, square(-2) == 4 검증)
문제 5. 함수 def get_age(age): if age < 0: raise ValueError; return age가 음수일 때 ValueError를 내는지 검증하는 pytest 테스트를 작성하세요. (pytest.raises 사용)
<details>
<summary>✅ 정답·해설 보기</summary>
1. 프로젝트마다 필요한 라이브러리 버전이 달라 충돌할 수 있는데, 가상환경이 각 프로젝트를 격리해 그 충돌을 막아주기 때문입니다.
2.
python -m venv .venv:.venv라는 폴더에 새 가상환경을 만든다.pip install -r requirements.txt: 그 파일에 적힌 라이브러리들을 한 번에 설치한다.
3.
def add(x, y): # 함수명 snake_case, 인자 쉼표 뒤 공백 result = x + y # 변수명 소문자, 연산자 주변 공백 return result
4.
def test_square(): assert square(3) == 9 assert square(-2) == 4
5.
import pytest def test_get_age_negative(): with pytest.raises(ValueError): get_age(-5)
</details>
◀️ 이전 장: 18. 타입 힌트 | ▶️ 다음 장: 20. 미니 프로젝트: 도서 관리 CLI