RHEL üzerinde Tox ile Python test otomasyonu
Selamlar, bu yazıda RHEL sunucu üzerinde tox'u kurup, birden fazla Python sürümüne karşı testlerimizi nasıl koşturduğumuza bakacağız. Konu basit ama bir-iki yerde insanın canını sıkan ufak detaylar var, ben de tam o detaylara değineceğim. Hadi başlayalım.
Bir Python kütüphanesi yazıyorsanız ya da farklı sunucularda farklı Python sürümleriyle karşılaşacak bir uygulama paketliyorsanız, 'benim makinemde çalışıyordu' cümlesi er ya da geç başınıza patlıyor. Tox tam burada devreye giriyor: izole virtualenv'ler kuruyor, her ortamda paketinizi sıfırdan yüklüyor, testleri çalıştırıyor ve sonucu net bir özetle veriyor. Yani aslında küçük bir CI'yi yerel makineye taşıyorsunuz.
Tox aslında ne yapıyor?
Tox bir tox.ini ya da pyproject.toml dosyası okur. Tanımladığınız her ortam (env) için şu adımları sırayla yapar:
- Temiz bir virtualenv açar.
- Paketinizi ve tanımladığınız bağımlılıkları kurar.
- Belirttiğiniz komutları çalıştırır (genelde
pytest). - Geçti / kaldı diye raporlar.
Bu kadar. Sihir yok; ama bu kadarı bile elle yapmaya kalktığınızda kafa şişiriyor.
Birden fazla Python sürümü kurmak
RHEL 9 varsayılan olarak Python 3.9 ile geliyor. Test matrisini büyütmek için AppStream'den ek sürümleri çekiyoruz:
sudo dnf install -y python3 python3-pip python3-devel
sudo dnf install -y python3.11 python3.11-pip python3.11-devel
sudo dnf install -y python3.12 python3.12-pip python3.12-devel
Burada python3.12-devel paketini atlamak ilk anda işe yaramış gibi görünür, ama bağımlılıklarınız içinde C uzantısı derleyen bir şey çıktığında (mesela psycopg2'nin binary olmayan sürümü) hata yer. Bence baştan -devel'i de kurun, dert etmeyin.
Tox'u kurmak
Tox'u sistem geneline değil, kullanıcı seviyesinde kurmak benim tercihim:
pip3 install --user tox
tox --version
Eğer tox komutu bulunamıyorsa ~/.local/bin dizini PATH'inizde olmayabilir. Bunu .bashrc'ye eklemek bir kerelik bir iş.
Ornek bir tox.ini
Kafamızda somutlaşması için küçük bir paket düşünelim. src/mypackage/__init__.py içinde iki fonksiyon olsun:
def add(a, b):
return a + b
def divide(a, b):
if b == 0:
raise ValueError('Sifira bolme yok')
return a / b
Test dosyamız tests/test_math.py:
import pytest
from mypackage import add, divide
def test_add():
assert add(2, 3) == 5
def test_divide_by_zero():
with pytest.raises(ValueError):
divide(1, 0)
Şimdi tox.ini:
[tox]
envlist = py39, py311, py312, lint
isolated_build = True
[testenv]
deps =
pytest
pytest-cov
commands =
pytest tests/ -v --cov=mypackage --cov-report=term-missing
[testenv:lint]
deps =
flake8
black
commands =
flake8 src/ tests/
black --check src/ tests/
tox komutunu çalıştırdığınızda dört ortam birden açılır, paketinizi her birine ayrı ayrı kurar ve testleri koşar. tox -e py311 derseniz tek ortam çalışır.
Paralel kosma ve yeniden olusturma
Üç Python sürümü ardı ardına kurulup testler çalışınca süre uzayabiliyor. Paralel modu deneyin:
tox -p auto
Bağımlılık değişip ortamlar bayatladığında ise:
tox -r
Bütün ortamları siler, sıfırdan kurar. CI dışında yerelde sık ihtiyaç duyduğum bir komut, çünkü requirements'a yeni bir paket eklediğinizde tox bunu otomatik fark etmiyor her zaman.
Bizim takildigimiz bir nokta
Şahsi tecrübeden söyleyeyim: bir keresinde RHEL 9 üzerinde py312 ortamı tox listesinde 'görünmüyor' diye uğraşmıştık. Sebebi gayet basitti aslında - paket kurulu değildi ama dnf sessizce başka bir paketi çözmüştü. python3.12 --version denediğimizde 'command not found' aldık. AppStream module setlerinde sürüm bazen gizli kalıyor; dnf module list python ile aktif modülü görmek bu durumda iş kurtarır. Üzerinde bir kahve harcadıktan sonra alışkanlık oldu, ilk iş onu çağırıyorum şimdi.
CI hattina baglamak
GitHub Actions tarafında matris tanımı şu kadar:
name: Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ['3.9', '3.11', '3.12']
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- run: pip install tox
- run: tox -e py
tox -e py o anki Python sürümünün ortamını seçiyor; matris bunu üç farklı versiyon için ayrı ayrı tetikliyor. Yerel tox.ini'yi değiştirmeden CI'yi büyütebiliyorsunuz, en hoş tarafı bu.
Sik karsilasilan tuzaklar
- isolated_build = True'yi unutmak:
pyproject.tomlile çalışıyorsanız bu satır olmazsa olmaz. Yoksa tox eski stilsetup.pydavranışına düşer ve modern build backend'lerinizi tanımaz. - deps icindeki surumleri sabitlememek: CI'da yeşil olan tox koşusu ertesi gün kırmızı dönerse, büyük ihtimalle
pytestya daflake8minor sürüm zıplaması yapmıştır. Kritik araçlar içinpytest>=8,<9gibi bir aralık koyun. - passenv'i bos birakmak: Tox güvenlik için ortam değişkenlerinizi temizler. CI'nin koyduğu
CI,GITHUB_ACTIONSgibi değişkenler de gider. Lazımsa açıkçapassenv = CI HOME GITHUB_*deyin. - Sistem Python'u ile karistirmak:
pip install toxderken root olmayın. Sistem paketleriyle çakışırsa toparlamak yorucu olur;pip3 install --user toxya dapipx install toxçok daha temiz.
Kapanis
Bu yazıda tox ile RHEL üzerinde çoklu Python sürümü test ortamını nasıl kurduğumuza, paralel koşmaya ve CI'a bağlamaya kısaca baktık. Bana sorarsanız tox, kütüphane yazan herkes için artık opsiyonel bir araç değil; matris testi olmadan farklı sürümlerde kırılan bir paketi yayınlamanın bedeli her zaman daha ağır oluyor. Umarım faydalı olur, bir sonraki yazıda görüşmek üzere.
