22편에서 pytest 로 함수의 동작 이 맞는지 검증했다. 동작이 맞아도 안 쓰는 import, 정리 안 된 변수, 사람마다 다른 들여쓰기 습관은 그대로 남는다. 이런 스타일 문제를 사람이 리뷰에서 매번 지적하는 대신, Ruff 와 pre-commit 으로 커밋·CI 단계에서 자동으로 걸러내는 흐름을 만든다.
한 줄 정의
Ruff 는 린트(오류·스타일 검사)와 코드 포매팅을 하나의 실행 파일로 처리하는 도구이고, pre-commit 은 그 검사를 커밋 시점에 자동으로 강제하는 Git hook 관리자다. 여기에 GitHub Actions를 더하면 로컬에서 검사를 건너뛴 커밋도 저장소에 들어오기 전에 다시 걸러낸다.
왜 도구 하나로 묶는가
Ruff 이전에는 코드 스타일을 검사하는 도구가 역할별로 나뉘어 있었다.
역할 | 기존 도구 | Ruff |
|---|---|---|
오류·스타일 린트 |
|
|
import 정렬 |
|
|
코드 포매팅 |
|
|
세 도구를 따로 설치·설정하던 것을 pyproject.toml 설정 하나로 합쳤다. Rust로 작성되어 있어 같은 검사를 Python으로 작성된 기존 도구보다 훨씬 빠르게 끝낸다는 점도 실무에서 체감되는 차이다. 다만 정적 타입 검사(mypy)는 Ruff의 역할이 아니다 — 린트·포맷과 타입 검사는 여전히 별개 도구다.
린트로 문제를 찾아내기
정리되지 않은 분석 스크립트 하나로 시작한다.
import pandas as pd
import os
def load_and_clean( path ):
df = pd.read_csv(path)
unused_var = 42
df=df.dropna()
return df
pip install ruff
ruff check analyze.py
I001 [*] Import block is un-sorted or un-formatted
--> analyze.py:1:1
F401 [*] `os` imported but unused
--> analyze.py:2:8
F841 Local variable `unused_var` is assigned to but never used
--> analyze.py:6:5
Found 3 errors.
[*] 2 fixable with the `--fix` option (1 hidden fix can be enabled with the `--unsafe-fixes` option).
규칙 코드 앞 글자가 검사 종류를 가리킨다.
코드 접두어 | 출처 | 의미 |
|---|---|---|
| pycodestyle | PEP 8 스타일 오류 |
| Pyflakes | 미사용 import·변수 같은 실제 오류 가능성 |
| isort | import 순서·그룹 정리 |
기본 설정은 이 중 Pyflakes(F)와 pycodestyle 오류 규칙 일부(E)만 켠 상태로 시작한다. 나머지 규칙군은 pyproject.toml 에서 필요한 만큼 추가로 켠다.
자동 수정과 포매팅
표시된 것 중 [*] 가 붙은 항목은 자동으로 고칠 수 있다. unused_var 처럼 값을 지워도 되는지 확신할 수 없는 경우는 기본 --fix 로는 건드리지 않는다.
ruff check --fix analyze.py
ruff format analyze.py
import pandas as pd
def load_and_clean(path):
df = pd.read_csv(path)
unused_var = 42
df = df.dropna()
return df
import 정렬과 미사용 os 제거는 --fix 가, 함수 인자·대입 연산자 앞뒤 공백은 ruff format 이 처리했다. unused_var 는 값을 실제로 쓰지 않았는데도 남아 있다 — 이런 경우까지 자동으로 지우게 하려면 --unsafe-fixes 를 따로 켜야 하며, 코드의 의도를 지울 위험이 있어 기본값에서는 꺼져 있다.
pyproject.toml 설정
프로젝트 전체에 적용할 규칙은 다른 설정과 함께 pyproject.toml 한 곳에 둔다.
[tool.ruff]
line-length = 88
[tool.ruff.lint]
extend-select = ["I"] # import 정렬(I) 규칙을 기본값에 추가
ignore = ["E501"] # 긴 줄은 포매터가 대부분 처리하므로 제외
[tool.ruff.format]
quote-style = "double"
indent-style = "space"
select 로 규칙을 통째로 바꾸는 대신 extend-select 로 기본값 위에 필요한 규칙만 더한다. 팀이 새 Ruff 버전으로 올릴 때 기본 규칙 집합이 늘어나도 설정을 다시 손볼 필요가 없다.
pre-commit으로 커밋 시점에 강제하기
로컬에서 ruff check 를 실행하는 걸 잊어버리면 검사 자체가 없던 일이 된다. pre-commit 은 git commit 직전에 정해둔 검사를 자동으로 실행하고, 실패하면 커밋 자체를 막는다.
pip install pre-commit
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.16.2
hooks:
- id: ruff-check
args: [--fix]
- id: ruff-format
이 저장소의 최신 README는 린트 훅 이름을
ruff-check로 쓴다. 예전 자료에 흔한id: ruff는 하위 호환을 위해 남겨둔 이전 이름이다.
pre-commit install
설치 후에는 커밋마다 자동으로 검사가 돈다. 정리 전 analyze.py 를 커밋해 보면, 훅이 파일을 고치고 나서 커밋을 중단시킨다.
$ git add analyze.py
$ git commit -m "add analyze.py"
ruff check...............................................................Failed
- hook id: ruff-check
- files were modified by this hook
Found 2 errors (2 fixed, 0 remaining).
ruff format..............................................................Failed
- hook id: ruff-format
- files were modified by this hook
1 file reformatted
Failed 라고 나와도 코드에 문제가 있다는 뜻이 아니라 훅이 파일을 고쳤다 는 뜻이다. 고쳐진 결과를 다시 스테이징하고 같은 명령을 반복하면, 이번에는 더 고칠 것이 없어 통과하고 커밋이 완료된다.
$ git add -A
$ git commit -m "add analyze.py"
ruff check...............................................................Passed
ruff format..............................................................Passed
[main c8464af] add analyze.py
1 file changed, 6 insertions(+)
GitHub Actions로 이중 방어하기
pre-commit은 --no-verify 로 건너뛸 수 있고, 다른 협업자의 로컬 환경에는 아예 설치돼 있지 않을 수도 있다. 그래서 Push 이후 서버 쪽에서 같은 검사를 한 번 더 돌린다.
name: CI - Lint & Test
on: [push, pull_request]
jobs:
quality-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-python@v7
with:
python-version: "3.11"
- name: Install dependencies
run: pip install ruff pytest
- name: Run ruff check
run: ruff check .
- name: Run ruff format check
run: ruff format --check .
- name: Run pytest
run: pytest
ruff format --check 는 로컬의 ruff format 과 달리 파일을 고치지 않고, 포맷이 필요한 파일이 있으면 그 사실만 알리고 실패로 끝난다. CI는 코드를 대신 고쳐주는 자리가 아니라 고칠 것이 남아 있는지 확인하는 자리이기 때문이다.
전체 흐름
개발자가 로컬에서 코드를 작성한다.
git commit순간 pre-commit이ruff-check와ruff-format을 실행한다. 문제가 있으면 자동 수정 후 커밋을 중단시켜 다시 확인하게 한다.통과한 커밋만
git push로 원격 저장소에 올라간다.GitHub Actions가 같은 검사와
pytest를 서버에서 다시 실행해, 로컬 훅을 건너뛴 경우까지 걸러낸다.
장점과 한계
구분 | 내용 |
|---|---|
장점 | 설정 파일 하나로 린트·정렬·포맷을 끝내고, 검사를 사람의 기억이 아니라 Git hook과 CI에 맡겨 빠뜨릴 일이 없다. |
한계 | 타입 오류는 잡지 못해 mypy 같은 별도 도구가 여전히 필요하고, |
자주 발생하는 문제
훅 최초 실행이 느리다. pre-commit install 이후 첫 커밋에서는 훅이 사용할 환경을 새로 내려받고 만드느라 시간이 걸린다. 이후 커밋부터는 만들어둔 환경을 재사용해 빨라진다.
커밋이 계속 실패하는 것처럼 보인다. files were modified by this hook 메시지가 뜨면 실패가 아니라 자동 수정이 일어났다는 신호다. git add -A 로 수정 결과를 다시 스테이징하지 않고 같은 커밋 명령만 반복하면 계속 막힌다.
로컬과 CI의 검사 결과가 다르다. pre-commit 의 rev 값과 CI에서 설치하는 Ruff 버전이 어긋나면 같은 코드에서도 다른 결과가 나올 수 있다. 두 곳의 버전을 같은 값으로 맞춰 둔다.
관련 문서
이 문서에서 다룬 CI 구성은 pytest로 데이터 분석 함수를 테스트하기 에서 만든 테스트를 그대로 실행 대상으로 쓴다. 검사 대상 프로젝트의 초기 가상환경·의존성 관리는 데이터 분석용 Python 개발 환경 만들기 를 먼저 본다.
참고 자료
Ruff 공식 문서 — Configuration (2026-08-10 확인)
Ruff 공식 문서 — Rules (2026-08-10 확인)
ruff-pre-commit 저장소 (2026-08-10 확인)
pre-commit 공식 사이트 (2026-08-10 확인)
댓글 0
댓글을 불러오는 중…