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
📌 핵심
핵심: 가상환경은 프로젝트별 격리된 Python·라이브러리 공간이다. 라이브러리 버전 충돌을 막고, 시스템 Python을 깨끗하게 유지한다. 새 프로젝트를 시작하면 가상환경부터 만드는 것이 기본 습관이다.

표준 도구: venv + pip

venv는 Python에 기본 포함된 가상환경 도구입니다. 터미널에서 다음과 같이 씁니다.

TEXT
# 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는 가상환경 생성·패키지 설치·버전 관리를 한 도구로 처리하고, 속도가 수십~수백 배 빠릅니다.

TEXT
# 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의 목적은 단 하나, 읽기 쉬운 코드입니다. 코드는 작성되는 횟수보다 읽히는 횟수가 훨씬 많으니까요.

몇 가지 대표 규칙을 봅시다.

PYTHON
# ❌ 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는 포매팅(코드 정렬)과 린팅(문제 검사)을 한 번에, 매우 빠르게 처리합니다.

TEXT
# 코드 검사 (문제점 찾기)
ruff check .

# 자동 수정 가능한 문제는 고치기
ruff check --fix .

# 코드 포매팅 (스타일 정리)
ruff format .
💡 팁
예전에는 포매팅에 black, 검사에 flake8, import 정렬에 isort 등 여러 도구를 따로 썼습니다. Ruff는 이들을 하나로 합쳤습니다. 핵심은 도구 이름이 아니라 "스타일은 사람이 손으로 맞추지 말고 도구에 맡긴다"는 습관입니다.
📌 핵심
핵심: PEP 8은 읽기 쉬운 코드를 위한 스타일 약속이고, 포매터(Ruff 등)는 그것을 자동으로 적용하는 도구다. 스타일 논쟁에 시간 쓰지 말고 도구에 맡겨라.

테스트: 코드가 맞는지 자동 검증

왜 테스트하는가

함수를 고쳤는데 다른 곳이 망가지지 않았을까? 새 기능이 기존 기능을 깨뜨리지 않았을까? 테스트(test)는 이를 자동으로 확인하는 코드입니다. 한 번 짜두면, 코드를 바꿀 때마다 "여전히 잘 동작하는가"를 즉시 검증할 수 있습니다.

assert로 시작하기

테스트의 핵심은 assert 문입니다. "이게 참이어야 한다"고 주장하고, 거짓이면 에러를 냅니다.

PYTHON
assert 2 + 2 == 4        # 참 → 아무 일 없음
assert 2 + 2 == 5        # 거짓 → AssertionError 발생!

테스트할 함수가 있다고 합시다.

PYTHON
# calculator.py
def add(a, b):
    return a + b

def divide(a, b):
    if b == 0:
        raise ValueError("0으로 나눌 수 없습니다")
    return a / b

assert로 직접 검증할 수 있습니다.

PYTHON
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를 쓰면 됩니다.

PYTHON
# 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)

터미널에서 실행하면 결과를 깔끔하게 보여줍니다.

TEXT
$ 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) 결과 표시
💡 팁
Python 표준 라이브러리에는 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. 다음 명령들이 각각 하는 일을 적으세요.

TEXT
python -m venv .venv
pip install -r requirements.txt

문제 3. 다음 코드를 PEP 8에 맞게 고치세요.

PYTHON
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.

PYTHON
def add(x, y):           # 함수명 snake_case, 인자 쉼표 뒤 공백
    result = x + y       # 변수명 소문자, 연산자 주변 공백
    return result

4.

PYTHON
def test_square():
    assert square(3) == 9
    assert square(-2) == 4

5.

PYTHON
import pytest

def test_get_age_negative():
    with pytest.raises(ValueError):
        get_age(-5)

</details>

◀️ 이전 장: 18. 타입 힌트 | ▶️ 다음 장: 20. 미니 프로젝트: 도서 관리 CLI