실행 환경과 모듈 구성
90분 안팎
학습 목표
로컬 가상환경에서 수집·변환·저장 모듈과 테스트를 구성하고 의존성을 기록합니다.
개념
다른 컴퓨터에서도 같은 순서로 실행합니다
앞 모듈에서 만든 그림이 맞더라도 담당자가 바뀐 뒤 실행하지 못하면 매일 갱신할 자료로 쓰기 어렵습니다. 보고서 작업을 반복 처리로 바꾸는 첫 일은 새 패키지를 설치하는 것이 아니라 실행의 출발점을 고정하는 것입니다. 이번 레슨에서는 프로젝트 루트, Python 실행 파일, 파일 경로와 테스트 명령을 함께 정합니다. 분석 결과를 바꾸지 않으면서 수집·변환·저장 코드를 나눌 준비를 합니다.
이 모듈의 미션 ZIP은 m06의 solution 전체에서 시작합니다. raw의 교통·날씨 원본, joined.csv, cleaning.py, out의 정제 파일, metrics-out의 지표, visual-out의 그림과 보고서를 보존합니다. 이전 산출물을 새 폴더에 손으로 다시 입력하지 않습니다. 이름만 같은 파일을 만들면 합성 표본의 행 수와 결측 정책이 달라질 수 있습니다. 기존 테스트는 이전 단계의 계약이 유지되는지 확인하는 안전망입니다.
한 번만 정하는 실습 환경
Python 3.9 이상을 준비하고 ZIP을 푼 폴더를 터미널의 현재 폴더로 삼습니다. 이 모듈의 파일 ETL은 csv, json, hashlib, urllib, sqlite3, unittest 등 표준 라이브러리만 사용합니다. 앞 모듈의 별도 matplotlib 실습을 다시 설치하지 않아도 미션의 SVG 보고서와 ETL 검사를 실행할 수 있습니다. requirements.txt의 주석은 외부 의존성이 없다는 결정을 기록합니다. 빈 설치 목록을 실패나 누락으로 해석하지 않습니다.
macOS와 Linux에서는 python3 -m venv .venv로 환경을 만들고 .venv/bin/python을 직접 호출합니다. Windows에서는 py -3 -m venv .venv로 만들고 .venv\Scripts\python.exe를 사용합니다. 활성화는 선택 사항입니다. 직접 경로를 쓰면 어느 인터프리터가 테스트를 실행하는지 명확해집니다. 가상환경은 패키지 설치 위치를 나누며 생성에 쓴 Python의 버전을 다른 버전으로 바꾸지 않습니다.
환경 폴더 자체는 제출물에 넣지 않습니다. 다른 컴퓨터에서 같은 절차로 다시 만들도록 Python 요구 버전, 외부 의존성 여부, 실행 명령을 README에 적습니다. 절대 경로가 들어 있는 가상환경을 복사하면 작성자 컴퓨터에서는 열려도 동료 컴퓨터에서는 실행 파일을 찾지 못할 수 있습니다. 실행 기록에는 실제 Python 버전과 운영체제를 적되 개인 홈 경로나 계정 정보를 콘텐츠 예제에 섞지 않습니다.
역할과 경로를 먼저 분리합니다
collect는 입력 바이트를 보존하고 출처와 체크섬을 남깁니다. transform은 입력 계약을 검사하고 기존 정제 함수를 호출합니다. load는 검증된 행만 SQLite로 저장합니다. run은 이 함수들을 순서대로 호출하는 진입점입니다. 파일을 세 개로 나눈 것만으로 단계 분리가 끝나지 않습니다. 각 함수의 입력, 반환값, 실패 방식과 파일을 쓰는 위치가 분명해야 개별 테스트가 가능합니다.
기본 입력은 joined.csv이며 수집 결과는 collected, 변환 결과는 etl-out, 저장 결과는 etl.sqlite입니다. 앞 모듈의 out이나 visual-out을 새 단계의 임시 폴더로 사용하지 않습니다. 같은 이름의 원본에 결과를 쓰면 다음 실행의 입력이 이미 바뀌어 처음과 다른 계산을 하게 됩니다. 경로 네 개를 목록으로 출력해 입력과 산출물의 역할을 설명하는 것이 첫 실습의 제출 기준입니다.
etl 폴더의 __init__.py는 패키지 경계를 나타내고 environment.py는 환경 설명 함수를 둡니다. 파일 이름을 json.py나 csv.py로 짓지 않습니다. 그런 파일이 표준 라이브러리보다 먼저 검색되면 json.loads가 없다는 AttributeError처럼 뜻밖의 오류가 발생합니다. 모듈을 가져오는 순간 수집이나 DB 쓰기가 일어나지 않도록 실제 실행은 함수 안과 __main__ 조건 아래에 둡니다.
테스트 실패를 작업 목록으로 읽습니다
starter는 정상적으로 가져올 수 있고 Python 버전과 의존성 검사는 통과하지만 경로 검사는 실패합니다. describe의 paths 목록에서 수집 위치를 입력과 다르게 고칩니다. assertEqual의 왼쪽은 실제 값, 오른쪽은 기대값입니다. 둘이 다르다는 메시지를 읽고 경로의 역할을 수정합니다. 기대값을 입력 파일 이름으로 바꾸면 테스트가 초록색이어도 원본 보존이라는 요구를 없애는 것입니다.
ModuleNotFoundError가 나면 파일의 존재보다 먼저 현재 폴더를 봅니다. tests 폴더 안으로 이동해 명령을 실행하면 프로젝트 패키지를 검색하지 못할 수 있습니다. ZIP 루트에서 python3 -m unittest discover -s tests -v를 실행합니다. AssertionError는 기능 비교 실패이며 ImportError와 SyntaxError는 테스트가 기능을 비교하기 전에 발생한 구성 문제입니다. 두 부류를 나누어 고쳐야 실패 개수만 보고 원인을 오해하지 않습니다.
따라하기의 짧은 코드는 각각 독립적으로 실행합니다. 실습 ZIP의 테스트는 ZIP 루트에서 실행하고 starter의 실패를 먼저 관찰한 뒤 자신의 구현을 고칩니다. solution은 마지막 대조용입니다. 뒤 레슨의 브라우저 입력은 JSON 배열이며 local 미션의 기본 입력은 결합 CSV입니다. 두 형식을 같은 것으로 가정하지 않습니다. JSON 필수 열의 개념을 익힌 뒤 CSV 전체 계약에 적용하는 순서입니다.
진입점을 점검합니다
python3 -m etl.run은 프로젝트 루트에서 패키지의 실행 모듈을 찾는 방식입니다. etl/run.py를 직접 실행하면서 상대 import를 쓰면 부모 패키지를 알 수 없다는 ImportError가 날 수 있습니다. 환경 레슨에서는 파일을 만드는 일보다 명령과 위치를 함께 설명하는 습관을 익힙니다. 다음 담당자가 README의 한 줄을 그대로 실행해 같은 검사에 도달하는지가 환경 구성의 완료 기준입니다.
더 읽기의 가상환경·패키징 장은 외부 의존성이 생겼을 때 직접 의존성과 설치 결과를 관리하는 방법을 확장합니다. 여기서는 사용하지 않는 패키지를 미리 설치하지 않고 네 단계의 경로와 테스트 시작점을 먼저 고정합니다. 환경을 단순하게 유지하면 수집 실패가 패키지 설치 문제인지 입력 자료 문제인지 구분하는 데 필요한 경우의 수가 줄어듭니다.
따라하기
환경과 역할 확인
Python 요구 범위와 외부 의존성 여부를 확인합니다. 구체적인 설치 버전은 자신의 실행 기록에 따로 적습니다.
import sys
print('python_supported', sys.version_info >= (3, 9))
print('external_dependencies', 0)실행 결과
python_supported True external_dependencies 0
입력과 단계 경로 확인
문자열이 다른지 확인한 뒤 실제 미션에서는 resolve한 경로의 포함 관계도 검사합니다.
from pathlib import Path
paths = ['joined.csv','collected','etl-out','etl.sqlite']
for role, path in zip(['source','snapshot','transformed','database'], paths):
print(role, Path(path).as_posix())
print('distinct', len(set(paths)) == len(paths))실행 결과
source joined.csv snapshot collected transformed etl-out database etl.sqlite distinct True
import 때 실행되지 않는 모듈
출력 위치를 직접 확인합니다. 모듈 가져오기는 조용하고 함수 호출 결과만 명시적으로 출력합니다.
import tempfile, pathlib, sys, importlib
with tempfile.TemporaryDirectory() as folder:
pathlib.Path(folder, 'traffic_stage.py').write_text("def count(): return 6\nif __name__ == '__main__': print(count())\n")
sys.path.insert(0, folder)
stage = importlib.import_module('traffic_stage')
print('import completed')
print('call', stage.count())실행 결과
import completed call 6
ZIP 검사와 실패 위치 확인
starter.zip을 푼 루트에서 아래 명령을 실행합니다. 기본 4개 테스트 통과입니다. starter는 경로 검사 1개 실패입니다. 실패의 actual과 expected를 읽고 TODO를 채운 뒤 다시 검사합니다. HTTP 통합은 이 작성 샌드박스의 포트 권한 때문에 실행하지 못했으며 기본 검사와 구분합니다.
python3 -m unittest discover -s tests -v확인 문제
실습
describe의 paths를 입력·수집 보존·변환·DB 순서로 수정합니다. 버전, 의존성, 조용한 import 검사도 유지합니다. README의 가상환경 명령으로 실행하고 테스트를 모두 통과시킵니다.
실행 명령
python3 -m unittest discover -s tests -v
기대 결과
기본 4개 테스트 통과입니다. starter는 경로 검사 1개 실패입니다.
모범 답안
모범 답안 내려받기더 읽기
면접 질문
- 공개 데이터로 만든 그래프의 신뢰성을 확인하는 방법을 설명해 주시면 됩니다.