Takip et

Shell Betikleri Testinde Stubbing’in Gücü

Shell betiklerini test etmek, özellikle dış bağımlılıklar söz konusu olduğunda zorlayıcı olabilir. Bu makale, ‘stubbing’ tekniğinin gücünü kullanarak test süreçlerinizi nasıl kolaylaştırabileceğinizi ve daha güvenilir hale getirebileceğinizi detaylıca açıklıyor.

Shell betikleri, otomasyon dünyasının isimsiz kahramanlarıdır. Sistem yönetimi görevlerinden CI/CD (Sürekli Entegrasyon/Sürekli Dağıtım) boru hatlarına kadar pek çok alanda karşımıza çıkarlar. Ancak, bu betikleri yazmak kolay olsa da, onları düzgün bir şekilde test etmek genellikle göz ardı edilen veya “çok zor” olarak etiketlenen bir konudur. Peki, bu zorluğun temelinde neler yatar? Öncelikle, shell betikleri genellikle sistemin diğer bileşenleriyle derinlemesine etkileşim halindedir. Örneğin, bir betik bir veritabanına bağlanabilir, bir API çağrısı yapabilir, dosya sistemi üzerinde işlemler gerçekleştirebilir ya da harici komut satırı araçlarını (curl, aws cli, kubectl gibi) çalıştırabilir. Bu tür dış bağımlılıklar, test ortamını kurmayı ve her bir testin izole bir şekilde çalışmasını sağlamayı oldukça karmaşık hale getirir.

Dahası, gerçek dış servislerle yapılan testler genellikle yavaş çalışır ve maliyetlidir. Her test koşumunda bir veri tabanına bağlanmak veya bir bulut servisine istek göndermek hem zaman alır hem de gereksiz yere kaynak tüketir. En önemlisi, bu bağımlılıklar testlerinizin kararsız olmasına yol açabilir. Örneğin, ağ sorunları, servis kesintileri veya dış API’deki beklenmedik değişiklikler, aslında çalışan betiğinizin testlerinin başarısız olmasına neden olabilir. Bu “flaky tests” (kararsız testler), geliştiricilerin güvenini zedeler ve otomasyonun faydalarını azaltır. Ayrıca, kritik sistem komutlarının (rm -rf gibi) yanlışlıkla gerçek ortamda çalıştırılma riski de cabasıdır. Bu durumlar, geliştiricileri genellikle test yazmaktan vazgeçirir veya sadece manuel testlere yönlendirir, bu da uzun vadede daha büyük sorunlara yol açar. İşte tam da bu noktada, stubbing devreye girer ve bu karmaşıklığı yönetmek için güçlü bir çözüm sunar. Shell betiklerinin doğası gereği esnek ve dinamik olması, aynı zamanda test edilebilirliği açısından bazı benzersiz zorlukları beraberinde getirir. Geliştiriciler, betiklerin farklı senaryolarda doğru tepki verdiğinden emin olmak isterler ancak dış sistemlere sürekli bağımlı kalmak istemezler. İşte bu çelişkiyi çözmek için stubbing gibi teknikler vazgeçilmez bir hal alır.

Uzman İpucu: Shell betiklerini test ederken karşılaşılan en büyük sorunlardan biri, dış bağımlılıkların testlerin tutarlılığını bozmasıdır. Stubbing, bu bağımlılıkları ortadan kaldırarak test ortamınızda tam kontrol sağlar.

Stubbing sh Nedir ve Temel Kavramları Nelerdir?

Stubbing, yazılım testlerinde, bir bileşenin dış bağımlılıklarını izole etmek için kullanılan temel bir tekniktir. Shell betikleri (sh, bash, zsh vb.) özelinde stubbing, bir betiğin çağırdığı harici komutları, fonksiyonları veya servisleri, önceden tanımlanmış ve kontrol edilebilir davranışlarla değiştirmek anlamına gelir. Böylece, test ettiğiniz betiğin gerçekten ne yaptığına odaklanabilir, dış faktörlerin etkisini ortadan kaldırabilirsiniz. Bu sayede, testleriniz daha hızlı, daha güvenilir ve daha tekrarlanabilir hale gelir.

Stubbing’in temel amacı, test edilen birimin (bu durumda shell betiği) izole bir ortamda çalışmasını sağlamaktır. Diyelim ki bir shell betiğiniz, uzak bir sunucudan veri indirmek için curl komutunu kullanıyor. Gerçek bir test ortamında, curl‘ın başarılı olması için internet bağlantısı, hedef sunucunun çalışır durumda olması ve doğru veriyi döndürmesi gerekir. Bu koşulların her zaman sağlanması zordur. Stubbing ile, curl komutunu geçici olarak kendi yazdığınız bir fonksiyona veya başka bir betiğe yönlendirerek, sanki curl çalışmış gibi, ancak sizin istediğiniz sonucu (başarılı indirme, hata mesajı, boş veri vb.) döndürmesini sağlayabilirsiniz. Bu, test senaryolarınızı çok daha esnek ve kontrol edilebilir kılar.

Peki, stubbing’i mocking veya faking gibi diğer test izolasyon tekniklerinden ayıran nedir? Kısaca açıklamak gerekirse:

  • Stubbing: Test edilen bileşenin dış bağımlılıklarının “davranışını” taklit eder. Genellikle belirli bir çıktı döndürmek veya belirli bir işlemi simüle etmekle ilgilenir. Stub’lar genellikle daha basit ve statiktir.
  • Mocking: Stubbing’e benzer, ancak daha gelişmiştir. Bir mock nesnesi, sadece davranış taklidi yapmakla kalmaz, aynı zamanda test edilen bileşenin bu bağımlılıkla nasıl etkileşime girdiğini (hangi parametrelerle çağrıldığı, kaç kez çağrıldığı vb.) de kaydeder. Bu sayede, testlerinizde etkileşimleri doğrulayabilirsiniz.
  • Faking: Gerçek bir bağımlılığın basitleştirilmiş bir uygulamasını sağlar. Örneğin, gerçek bir veritabanı yerine, bellekte çalışan basit bir veritabanı uygulamasını kullanmak faking’e bir örnektir.

Shell betikleri dünyasında stubbing, genellikle iki ana yöntemle gerçekleştirilir:

  1. PATH Manipülasyonu: Betiğin aradığı harici komutların (ls, curl, grep vb.) yerine, kendi sahte versiyonlarımızı içeren bir dizini PATH ortam değişkeninin başına eklemek. Betik bir komut çağırdığında, kabuk önce bizim sahte komutlarımızı içeren dizine bakar ve onu bulursa, bizim versiyonumuzu çalıştırır.
  2. Fonksiyon Üzerine Yazma (Function Overriding): Eğer betik içinde zaten bir shell fonksiyonu tanımlanmışsa veya biz geçici olarak bir fonksiyonu yeniden tanımlayabilirsek, betiğin o fonksiyonu çağırdığında bizim sahte versiyonumuzun çalışmasını sağlayabiliriz. Bu, genellikle mevcut betik fonksiyonlarını veya sistem komutlarının kabuk fonksiyonları olarak tanımlanmış hallerini hedeflemek için kullanılır.

Bu teknikler sayesinde, gerçek sistemden tamamen izole edilmiş, öngörülebilir ve hızlı test ortamları oluşturabiliriz. Böylece, geliştirme sürecindeki verimliliğiniz artar ve daha sağlam, hatasız shell betikleri üretebilirsiniz. Stubbing, aynı zamanda CI/CD süreçlerinde otomatik testlerin temelini oluşturur ve dağıtım öncesi güvenilirliği önemli ölçüde artırır. Bu yöntemler, özellikle betiklerinizin beklenmedik hatalara veya dış sistemdeki değişikliklere nasıl tepki verdiğini test etmek için paha biçilmezdir. Gelin şimdi bu temel kavramları pratik örneklerle daha yakından inceleyelim.

Stubbing ile İlk Adımlar: Basit Bir Örnek Nasıl Başlar?

Şimdi stubbing’in pratik dünyasına adım atalım. İlk olarak, PATH ortam değişkenini manipüle ederek harici bir komutu nasıl stub’layacağımızı görelim. Bu yöntem, shell betiklerinizin dışarıdan çağırdığı herhangi bir komutu (örneğin curl, git, docker, kubectl) taklit etmek için oldukça etkilidir. Adım adım ilerleyelim:

Vaka Analizi 1: Uzak Bir Kaynaktan Veri Çeken Betiği Test Etme

Diyelim ki, uzak bir API’den kullanıcı listesi çeken ve bunu işleyen basit bir shell betiğiniz var. Betik, curl komutunu kullanarak HTTP isteği yapıyor. Biz, curl‘ın gerçek bir API’ye gitmesini engellemek ve bunun yerine kendi kontrolümüzdeki bir JSON çıktısı döndürmesini sağlamak istiyoruz.

İşte test etmek istediğimiz betik (get_users.sh):


#!/bin/bash

# Uzak bir API'den kullanıcıları çeker
fetch_users() {
    local api_url="https://api.example.com/users"
    local response=$(curl -s "$api_url") # -s: sessiz mod

    if [ $? -eq 0 ]; then
        echo "$response" | jq '.' # JSON çıktısını formatla (jq yüklü olmalı)
        return 0
    else
        echo "Hata: Kullanıcılar çekilemedi." >&2
        return 1
    fi
}

# Eğer betik doğrudan çalıştırıldıysa
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
    fetch_users
fi
    

Bu betik, curl ve jq komutlarına bağımlıdır. Biz sadece curl'ı stub'layarak test etmek istiyoruz. İşte test betiğimiz (test_get_users.sh) ve curl stub'ımız (test_stubs/curl):

1. Stub Dizini Oluşturma ve Sahte Komutu Yazma:

Öncelikle, sahte komutlarımızı barındıracak bir dizin oluşturalım. Örneğin, test_stubs/:


mkdir -p test_stubs
    

Şimdi, curl komutunu taklit edecek olan betiği test_stubs/curl yoluna oluşturalım:


# test_stubs/curl içeriği
#!/bin/bash
echo '{"status": "success", "data": [{"id": 1, "name": "Alice"}, {"id": 2, "name": "Bob"}]}'
exit 0
    

Bu sahte curl betiğine çalıştırma izni verelim:


chmod +x test_stubs/curl
    

2. Test Betiğini Yazma (PATH Manipülasyonu ile):

Şimdi de testlerimizi yönetecek ana betiği (run_tests.sh) yazalım. Bu betik, PATH ortam değişkenini manipüle ederek, bizim sahte curl'ımızın gerçek curl'dan önce bulunmasını sağlayacak:


# run_tests.sh içeriği
#!/bin/bash

# Test ortamını ayarla
TEST_STUBS_DIR="./test_stubs"
ORIGINAL_PATH="$PATH" # Orijinal PATH'i sakla

setup_test_env() {
    # Stub dizinimizi PATH'in en başına ekliyoruz
    export PATH="$TEST_STUBS_DIR:$PATH"
    echo "PATH güncellendi: $PATH"
}

cleanup_test_env() {
    # Testler bittikten sonra PATH'i eski haline getiriyoruz
    export PATH="$ORIGINAL_PATH"
    echo "PATH eski haline getirildi: $PATH"
    rm -rf "$TEST_STUBS_DIR" # Stub dizinini de temizleyelim
}

# Hata durumunda cleanup yapmayı garantile
trap cleanup_test_env EXIT

run_test_case_success() {
    echo "--- Test Durumu: Başarılı API Yanıtı ---"
    # get_users.sh betiğini çalıştır
    output=$(./get_users.sh)
    expected_output='{
  "status": "success",
  "data": [
    {
      "id": 1,
      "name": "Alice"
    },
    {
      "id": 2,
      "name": "Bob"
    }
  ]
}'
    if [[ "$output" == "$expected_output" ]]; then
        echo "Test BAŞARILI: Beklenen çıktı alındı."
    else
        echo "Test BAŞARISIZ: Beklenen çıktı ile uyuşmuyor."
        echo "Beklenen:\n$expected_output"
        echo "Alınan:\n$output"
        return 1
    fi
}

run_test_case_failure() {
    echo "--- Test Durumu: Hatalı API Yanıtı (curl hatası) ---"
    # Bu test için farklı bir curl stub'ı kullanalım
    echo '#!/bin/bash' > "$TEST_STUBS_DIR/curl"
    echo 'echo "curl: (6) Could not resolve host: api.example.com" >&2' >> "$TEST_STUBS_DIR/curl"
    echo 'exit 6' >> "$TEST_STUBS_DIR/curl"
    chmod +x "$TEST_STUBS_DIR/curl"

    output=$(./get_users.sh 2>&1) # Hata çıktısını da yakala
    if echo "$output" | grep -q "Hata: Kullanıcılar çekilemedi."; then
        echo "Test BAŞARILI: Hata mesajı doğru şekilde yakalandı."
    else
        echo "Test BAŞARISIZ: Hata mesajı bekleniyor ancak bulunamadı."
        echo "Alınan:\n$output"
        return 1
    fi
}

# Testleri çalıştır
setup_test_env
run_test_case_success
run_test_case_failure

echo "Tüm testler tamamlandı."
    

Bu test betiğini çalıştırılabilir hale getirin: chmod +x run_tests.sh. Ardından ./run_tests.sh ile çalıştırdığınızda, get_users.sh betiğinizin gerçek curl yerine sizin sahte curl'ınızı kullandığını göreceksiniz. Bu yöntem, shell betiklerini dış bağımlılıklardan izole etmek için oldukça güçlü ve esnektir. Özellikle farklı hata senaryolarını (ağ kesintisi, sunucu hatası vb.) simüle etmek istediğinizde, sahte komutların çıkış kodlarını ve hata mesajlarını kolayca manipüle edebilirsiniz.

Unutmamak gerekir ki, trap cleanup_test_env EXIT komutu, betik herhangi bir nedenle sonlandığında (başarılı veya hatalı), cleanup_test_env fonksiyonunun çalışmasını garanti eder. Bu, test ortamınızı temizlemenin ve PATH'i eski haline getirmenin kritik bir yoludur. Aksi takdirde, ortam değişkenleriniz kalıcı olarak değişebilir ve diğer betiklerinizi etkileyebilir. Bu ilk adımda, PATH manipülasyonunun ne kadar güçlü ve basit bir stubbing mekanizması sağladığını görmüş olduk. Şimdi, daha incelikli bir stubbing yöntemine, yani fonksiyonları geçici olarak geçersiz kılmaya geçelim.

Fonksiyonları Geçici Olarak Geçersiz Kılma: Yerel Stubbing Nasıl Yapılır?

PATH manipülasyonu, harici komutları stub'lamak için mükemmel bir yöntemdir. Ancak, bazen betiğiniz içinde tanımlanmış bir shell fonksiyonunun davranışını değiştirmek isteyebilirsiniz. İşte burada fonksiyonları geçici olarak geçersiz kılma (function overriding) tekniği devreye girer. Bu yöntem, özellikle betik içindeki yardımcı fonksiyonları veya karmaşık iş mantığını test ederken çok işe yarar.

Shell ortamında, bir fonksiyonu tanımladığınızda veya yeniden tanımladığınızda, bu, geçerli shell oturumu içinde önceki tanımlamasını geçersiz kılar. Bu özelliği test senaryolarımızda kullanarak, betiğimizin beklenen davranışı sergileyip sergilemediğini kontrol edebiliriz.

Vaka Analizi 2: Bir Betik İçindeki Yardımcı Fonksiyonu Stub'lama

Şimdi, bir dosyanın varlığını kontrol eden ve sonra ona göre bir işlem yapan bir betik düşünelim. Betiğimiz, check_file_exists adında bir yardımcı fonksiyon kullanıyor.

İşte test etmek istediğimiz betik (process_file.sh):


#!/bin/bash

# check_file_exists: Dosyanın varlığını kontrol eder
# Parametre: $1 = kontrol edilecek dosya yolu
# Çıktı: 0 eğer dosya varsa, 1 eğer yoksa
check_file_exists() {
    local file_path="$1"
    if [[ -f "$file_path" ]]; then
        echo "Dosya '$file_path' mevcut."
        return 0
    else
        echo "Hata: Dosya '$file_path' bulunamadı." >&2
        return 1
    fi
}

# process_data: Dosya mevcutsa veriyi işler
process_data() {
    local data_file="$1"
    if check_file_exists "$data_file"; then
        echo "Dosya '$data_file' mevcut, veri işleniyor..."
        # Gerçek dünya senaryosunda burada dosya içeriği okunur, işlenir vb.
        echo "Veri başarıyla işlendi."
        return 0
    else
        echo "Veri işleme iptal edildi çünkü dosya bulunamadı." >&2
        return 1
    fi
}

# Eğer betik doğrudan çalıştırıldıysa
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
    process_data "data.txt"
fi
    

Bu betikte, process_data fonksiyonu, check_file_exists fonksiyonunun sonucuna göre hareket ediyor. Bizim amacımız, check_file_exists fonksiyonunu stub'lamak ve gerçek dosya sistemi kontrolü yapmadan onun başarılı veya başarısız olduğunu simüle etmek. Böylece, process_data'nın farklı durumlarla nasıl başa çıktığını test edebiliriz.

Test Betiği (run_function_tests.sh):


#!/bin/bash

# Orijinal betiği kaynak olarak dahil edelim
# Bu sayede betiğin fonksiyonlarını kendi ortamımızda tanımlamış olacağız.
# Ancak bu fonksiyonları stub'layacağımız için aslında kendi tanımlarımız geçerli olacak.
# Eğer kaynak dosyanın fonksiyonlarını direkt override ediyorsak,
# aslında source etmeye gerek kalmaz, sadece betiği çalıştırırken override ederiz.
# Ancak burada bağımsız test betiği içinde override edeceğiz.

# Temizlik fonksiyonu
cleanup_test_env() {
    echo "Temizlik yapılıyor..."
    unset -f check_file_exists # Stub fonksiyonunu kaldır
}
trap cleanup_test_env EXIT

run_test_case_file_exists() {
    echo "--- Test Durumu: Dosya Mevcut (Stub) ---"
    # check_file_exists fonksiyonunu stub'layalım
    # Bu fonksiyon, her çağrıldığında dosyanın var olduğunu bildirecek
    check_file_exists() {
        echo "Stub: Dosya '$1' mevcut olarak kabul edildi."
        return 0
    }
    
    # process_file.sh betiğini çalıştır, ama içindeki process_data'yı çağıracağız
    # Bu testte process_file.sh'nin kendisini çalıştırmak yerine,
    # içindeki process_data fonksiyonunu doğrudan çağırabiliriz
    # Veya betiğin tamamını çalıştırıp fonksiyonun override edildiğinden emin olabiliriz.
    # Kolaylık için betiği kaynak olarak alıp fonksiyona doğrudan erişelim.
    
    # ÖNEMLİ: Eğer process_file.sh'yi kaynak olarak almak istemiyorsanız,
    # ve betik zaten fonksiyonları çağırıyorsa,
    # o zaman export -f check_file_exists kullanmanız ve
    # test ettiğiniz betiği ayrı bir alt kabukta çalıştırmanız gerekebilir.
    # Ancak basitlik için burada aynı kabukta override ediyoruz.

    # Eğer betik ana fonksiyonu olarak çalışıyorsa:
    # output=$(./process_file.sh 2>&1)
    # Bu durumda, betiğin en üstüne source komutu eklemek mantıklı olabilir
    # ya da betiği doğrudan test betiği içinden çağırırken override yapmak.

    # Daha temiz bir yöntem: Test için bir yardımcı betik oluşturalım
    # ve fonksiyonları orada override edip ana betiği öyle çağıralım.

    # Şimdi daha direkt bir yaklaşımla, betiği bir alt kabukta çalıştırırken override edelim
    # Bu, betiğin orijinal tanımını etkilemeyecek.
    # Ancak eğer betik içinde çağrılan bir fonksiyonu override etmek istiyorsak,
    # genelde test betiğinin içinden source edip fonksiyonu override etmek daha kolaydır.
    # Burada source kullanarak betik fonksiyonlarını geçerli kabuğa yükleyelim
    source ./process_file.sh

    output=$(process_data "test_data.txt")
    
    if echo "$output" | grep -q "Veri başarıyla işlendi."; then
        echo "Test BAŞARILI: Dosya mevcut olarak kabul edildi ve veri işlendi."
    else
        echo "Test BAŞARISIZ: Dosya mevcut olarak kabul edilmesine rağmen veri işlenmedi."
        echo "Alınan:\n$output"
        return 1
    fi
}

run_test_case_file_not_exists() {
    echo "--- Test Durumu: Dosya Yok (Stub) ---"
    # check_file_exists fonksiyonunu tekrar stub'layalım, bu kez dosyanın olmadığını belirtecek
    check_file_exists() {
        echo "Stub: Dosya '$1' mevcut değil olarak kabul edildi." >&2
        return 1
    }

    # process_data fonksiyonunu çalıştır
    output=$(process_data "non_existent_file.txt" 2>&1) # Hata çıktısını da yakala
    
    if echo "$output" | grep -q "Veri işleme iptal edildi çünkü dosya bulunamadı."; then
        echo "Test BAŞARILI: Dosya yok olarak kabul edildi ve işlem iptal edildi."
    else
        echo "Test BAŞARISIZ: Dosya yok olarak kabul edilmesine rağmen işlem iptal edilmedi."
        echo "Alınan:\n$output"
        return 1
    fi
}

# Testleri çalıştır
run_test_case_file_exists
run_test_case_file_not_exists
echo "Tüm fonksiyon testleri tamamlandı."
    

run_function_tests.sh betiğini çalıştırılabilir hale getirin: chmod +x run_function_tests.sh. Ardından ./run_function_tests.sh ile çalıştırdığınızda, process_file.sh içindeki check_file_exists fonksiyonunun yerine sizin tanımladığınız sahte fonksiyonların çalıştığını göreceksiniz. Bu teknik, özellikle bir betiğin iç mantığını veya birden fazla fonksiyonun etkileşimini test ederken çok kullanışlıdır.

Uzman İpucu: Fonksiyonları geçersiz kılarken, stub fonksiyonlarınızın orijinal fonksiyonun beklediği parametreleri doğru şekilde ele aldığından ve beklenen çıkış kodunu döndürdüğünden emin olun. Bu, testlerinizin gerçekçi olmasını sağlar.

Fonksiyon stubbing'i, PATH manipülasyonuna kıyasla daha granüler kontrol sağlar. Tek bir betik içindeki belirli bir davranışı hedeflemenize olanak tanır. Özellikle karmaşık betiklerde, hangi bağımlılıkların gerçek kalması gerektiğini ve hangilerinin stub'lanması gerektiğini daha hassas bir şekilde belirleyebilirsiniz. Bu iki temel teknikle, shell betiklerinizi test etme yeteneğiniz önemli ölçüde artacaktır. Ancak stubbing sadece temel kavramlardan ibaret değildir; ileri düzey ipuçları ve püf noktalarıyla test stratejinizi daha da geliştirebilirsiniz.

Daha Karmaşık Senaryolar İçin İpuçları ve Püf Noktaları Nelerdir?

Temel stubbing tekniklerini öğrendikten sonra, sıra daha karmaşık senaryolarla başa çıkmaya geliyor. Shell betik testlerini daha güçlü, esnek ve güvenilir hale getirecek bazı ileri düzey ipuçları ve püf noktaları aşağıdadır.

1. Test Helper Betikleri ve Test Çerçeveleri Kullanımı

Büyük projelerde, her test dosyası için aynı setup ve teardown mantığını tekrarlamak verimsizdir. Bu nedenle, test yardımcı betikleri (test helper scripts) veya basit test çerçeveleri kullanmak, kod tekrarını azaltır ve testlerinizi daha düzenli hale getirir. Bu yardımcı betikler genellikle şunları içerir:

  • Ortam değişkenlerini ayarlama (PATH manipülasyonu gibi).
  • Geçici dizinler oluşturma ve temizleme.
  • Stub komutlarını veya fonksiyonlarını tanımlama.
  • Test sonuçlarını biçimlendirme ve raporlama.

Örneğin, tüm testleriniz için ortak bir test_utils.sh dosyası oluşturabilir ve test betiklerinizin başında bunu source edebilirsiniz. Bu, testlerinizin daha yönetilebilir olmasını sağlar.

2. Stub'a Gelen Parametreleri Doğrulama

Bazen bir komutun veya fonksiyonun sadece çalışıp çalışmadığını değil, aynı zamanda hangi parametrelerle çağrıldığını da doğrulamak istersiniz. Bu, "mocking"e daha yakın bir davranıştır ve stub'larınızı daha akıllı hale getirmenizi sağlar. Sahte komutunuz veya fonksiyonunuz içinde, çağrı parametrelerini bir dosyaya yazabilir veya bir ortam değişkenine kaydedebilir, daha sonra test betiğiniz bu değeri okuyarak doğrulamayı yapabilir.

Örnek test_stubs/my_command:


#!/bin/bash
echo "$@" >> "$TEST_STUBS_DIR/my_command_calls.log"
echo "Sahte çıktı"
exit 0
    

Test betiğinizde ise:


# ... test_stubs/my_command_calls.log içeriğini oku ve doğrula ...
expected_params="--option value --input /path/to/file"
actual_params=$(head -n 1 "$TEST_STUBS_DIR/my_command_calls.log")

if [[ "$actual_params" == "$expected_params" ]]; then
    echo "Parametreler doğru şekilde iletildi. BAŞARILI."
else
    echo "Parametreler uyuşmuyor. BAŞARISIZ."
fi
    

Bu yöntem, betiğinizin dış bağımlılıklarla doğru şekilde etkileşime girdiğinden emin olmanızı sağlar.

3. Dinamik Çıkış Kodları ve Hata Senaryoları

Gerçek dünyada komutlar her zaman başarılı olmaz. Ağ hataları, yetkilendirme sorunları veya geçersiz girişler nedeniyle farklı hata kodları döndürebilirler. Stub'larınızın bu senaryoları taklit edebilmesi, betiğinizin hata yönetimini test etmek için kritik öneme sahiptir. Stub'unuzu, bir ortam değişkeninin değerine veya çağrı parametrelerine göre farklı çıkış kodları döndürecek şekilde yapılandırabilirsiniz.

Örnek test_stubs/another_command:


#!/bin/bash
if [[ "$1" == "--fail" ]]; then
    echo "Hata durumu simüle ediliyor." >&2
    exit 1
else
    echo "Başarılı durum simüle ediliyor."
    exit 0
fi
    

Test betiğinizde:


# Başarılı senaryo
unset MOCK_BEHAVIOR
output=$(dynamic_command)
if [ $? -eq 0 ]; then ... fi

# Hata senaryosu
export MOCK_BEHAVIOR="error"
output=$(dynamic_command --fail 2>&1)
if [ $? -ne 0 ]; then ... fi
    

4. Geçici Dizinler ve Temizlik (mktemp ve trap)

Testler genellikle geçici dosyalar veya dizinler oluşturur. Bu kaynakların her test koşumu sonrasında temizlenmesi, test ortamınızın tutarlı kalması için çok önemlidir. mktemp komutu geçici dosya ve dizinler oluşturmak için idealdir. trap komutu ise bir shell betiği sonlandığında (başarılı veya hatalı) belirli bir komutun veya fonksiyonun çalıştırılmasını sağlar.


#!/bin/bash

# Geçici dizin oluştur
TEST_TEMP_DIR=$(mktemp -d)
echo "Geçici dizin oluşturuldu: $TEST_TEMP_DIR"

# Testler bittikten sonra temizliği yapacak trap komutu
cleanup() {
    echo "Geçici dizin temizleniyor: $TEST_TEMP_DIR"
    rm -rf "$TEST_TEMP_DIR"
}
trap cleanup EXIT # Betik başarılı veya başarısız olsun, her zaman çalışır

# Testler burada çalışır...
echo "Testler çalışıyor..."
touch "$TEST_TEMP_DIR/test_file.txt"
ls "$TEST_TEMP_DIR"

# ...
    

Bu yapı, testlerinizin birbirinden bağımsız olmasını ve sisteminizde gereksiz dosyalar bırakmamasını garantiler.

5. Ortam Değişkenleriyle Dinamik Stub Davranışı

Stub'larınızın davranışını test betiğinizden ortam değişkenleri aracılığıyla kontrol edebilirsiniz. Bu, aynı stub komutunu farklı test senaryolarında farklı şekillerde davranacak şekilde kullanmanıza olanak tanır.


# test_stubs/dynamic_command
#!/bin/bash
if [[ "$MOCK_BEHAVIOR" == "error" ]]; then
    echo "Dinamik hata!" >&2
    exit 1
else
    echo "Dinamik başarı!"
    exit 0
fi
    

Test betiğinizde:


# Başarılı senaryo
unset MOCK_BEHAVIOR
output=$(dynamic_command)
# ... kontrol et ...

# Hata senaryosu
export MOCK_BEHAVIOR="error"
output=$(dynamic_command 2>&1)
# ... kontrol et ...
unset MOCK_BEHAVIOR # Temizlemeyi unutma
    

Bu ipuçları, shell betik testlerinizi daha sağlam, ölçeklenebilir ve yönetilebilir hale getirmenize yardımcı olacaktır. Karmaşık iş mantıklarını ve çoklu dış bağımlılıkları içeren betikleri test ederken bu yöntemlerin bir kombinasyonunu kullanmak, geliştirme sürecinizde size büyük avantaj sağlayacaktır. Unutmayın, iyi yazılmış testler, betiğinizin güvenilirliğini artırmanın ve gelecekteki değişikliklere karşı onu korumanın en iyi yoludur.

Gerçek Dünya Vaka Analizi: CI/CD Ortamında Stubbing Kullanımı

Stubbing, sadece yerel geliştirme ortamında değil, Sürekli Entegrasyon/Sürekli Dağıtım (CI/CD) boru hatlarında da paha biçilmez bir rol oynar. CI/CD, yazılımın daha hızlı ve güvenilir bir şekilde teslim edilmesini sağlayan bir dizi otomasyon uygulamasıdır. Bu süreçte, shell betiklerinin doğru çalıştığından emin olmak kritik öneme sahiptir.

Neden CI/CD'de Stubbing'e İhtiyaç Duyarız?

  1. Hız ve Verimlilik: CI/CD boru hatları hızlı geri bildirim döngüleri gerektirir. Gerçek harici servislerle (veritabanları, bulut API'leri vb.) her test koşumunda etkileşim kurmak, test süresini önemli ölçüde uzatabilir. Stubbing, bu harici bağımlılıkları taklit ederek testleri saniyeler içinde tamamlamanızı sağlar.
  2. Güvenilirlik ve Tekrarlanabilirlik: Gerçek servisler kararsız olabilir; ağ gecikmeleri, servis kesintileri veya veri tutarsızlıkları gibi durumlar testlerinizi başarısız kılabilir, aslında betiğinizde bir hata olmasa bile. Stubbing, bu tür dış faktörleri ortadan kaldırarak testlerinizin her zaman aynı koşullarda çalışmasını garantiler ve "flaky" testlerin önüne geçer.
  3. Maliyet Tasarrufu: Bulut tabanlı servislerle her test koşumunda etkileşim kurmak maliyetli olabilir. Stubbing ile gerçek servis çağrılarını minimize ederek bulut faturanızdan tasarruf edebilirsiniz.
  4. İzolasyon ve Kapsamlı Test: Stubbing, belirli hata senaryolarını (örneğin, API'nin 500 hata kodu döndürmesi, ağ zaman aşımı) gerçekçi bir şekilde simüle etmenize olanak tanır. Gerçek bir ortamda bu senaryoları kasıtlı olarak tetiklemek zor veya imkansız olabilir. Stub'lar sayesinde, betiğinizin her türlü duruma nasıl tepki verdiğini kapsamlı bir şekilde test edebilirsiniz.

CI/CD Boru Hattı Örneği

Şimdi, bir CI/CD boru hattında stubbing'in nasıl kullanılabileceğine dair basitleştirilmiş bir senaryo oluşturalım. Diyelim ki, bir bulut depolama hizmetine (örneğin AWS S3) dosya yükleyen bir shell betiğimiz var. Bu betik, aws s3 cp komutunu kullanıyor.

Yüklenecek Betik (upload_file.sh):


#!/bin/bash

# Belirtilen dosyayı S3'e yükler
upload_to_s3() {
    local source_file="$1"
    local s3_bucket="$2"
    local s3_path="$3"

    if [[ -z "$source_file" || -z "$s3_bucket" || -z "$s3_path" ]]; then
        echo "Kullanım: upload_to_s3   " >&2
        return 1
    fi

    echo "Dosya '$source_file' S3 bucket '$s3_bucket' yoluna '$s3_path' yükleniyor..."
    aws s3 cp "$source_file" "s3://$s3_bucket/$s3_path"

    if [ $? -eq 0 ]; then
        echo "Yükleme BAŞARILI: $source_file -> s3://$s3_bucket/$s3_path"
        return 0
    else
        echo "Yükleme BAŞARISIZ!" >&2
        return 1
    fi
}

if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
    # Örnek kullanım (gerçekte parametrelerle gelir)
    upload_to_s3 "local_data.txt" "my-prod-bucket" "data/latest.txt"
fi
    

CI/CD Test Adımı (ci_test_script.sh):

Bu betik, CI/CD ortamında çalıştırılacak testleri içeriyor. aws komutunu stub'layarak gerçek S3 işlemlerini engelliyoruz.


#!/bin/bash

echo "CI/CD Testleri Başlatılıyor..."

# --- Ortam Ayarları ---
TEST_STUBS_DIR="./ci_test_stubs"
mkdir -p "$TEST_STUBS_DIR"
ORIGINAL_PATH="$PATH"

setup_ci_env() {
    export PATH="$TEST_STUBS_DIR:$PATH"
    echo "CI PATH güncellendi: $PATH"
}

cleanup_ci_env() {
    echo "CI Ortamı Temizleniyor..."
    export PATH="$ORIGINAL_PATH"
    rm -rf "$TEST_STUBS_DIR"
}
trap cleanup_ci_env EXIT

# --- AWS CLI Stub'ı ---
# Test_stubs/aws içeriği:
# Bu stub, aws komutunun cp alt komutunu yakalar ve her zaman başarılıymış gibi davranır.
# Gerçekte ise S3'e hiçbir şey yüklemez.
cat << 'EOF' > "$TEST_STUBS_DIR/aws"
#!/bin/bash
if [[ "$1" == "s3" && "$2" == "cp" ]]; then
    echo "Stub: S3 'cp' komutu yakalandı. Simüle edilmiş başarı."
    echo "Simüle edilmiş yükleme: $3 -> $4"
    exit 0
else
    # Diğer aws komutlarını varsayılan olarak başarısız yapabiliriz veya
    # gerçek aws'e düşürmek için PATH'i ayarlayabiliriz.
    echo "Stub: Bilinmeyen aws komutu: $@" >&2
    exit 1
fi
EOF
chmod +x "$TEST_STUBS_DIR/aws"
echo "AWS CLI stub hazır."

# --- Test Durumları ---

test_upload_success() {
    echo "--- Test Durumu: S3 Yükleme Başarılı ---"
    # Geçici bir test dosyası oluştur
    echo "Test verisi" > "$TEST_STUBS_DIR/temp_data.txt"
    local output=$(./upload_file.sh "$TEST_STUBS_DIR/temp_data.txt" "my-test-bucket" "test/data.txt")
    local status=$?

    if [ $status -eq 0 ] && echo "$output" | grep -q "Yükleme BAŞARILI"; then
        echo "Test BAŞARILI: Dosya başarılı şekilde yüklendi (simüle edildi)."
    else
        echo "Test BAŞARISIZ: Yükleme başarısız oldu."
        echo "Çıktı: $output"
        return 1
    fi
}

test_upload_failure_no_args() {
    echo "--- Test Durumu: Argüman Eksikliği ---"
    local output=$(./upload_file.sh 2>&1) # Hata çıktısını da yakala
    local status=$?

    if [ $status -ne 0 ] && echo "$output" | grep -q "Kullanım: upload_to_s3"; then
        echo "Test BAŞARILI: Argüman eksikliği doğru şekilde yakalandı."
    else
        echo "Test BAŞARISIZ: Argüman eksikliği beklenirken, farklı bir sonuç alındı."
        echo "Çıktı: $output"
        return 1
    fi
}

# --- Testleri Çalıştır ---
setup_ci_env
test_upload_success
test_upload_failure_no_args

echo "Tüm CI/CD testleri tamamlandı."
    

Bu ci_test_script.sh betiği, bir CI/CD aracı (örneğin Jenkins, GitHub Actions, GitLab CI) tarafından çalıştırıldığında, upload_file.sh betiğinin gerçek AWS komutlarını çağırmak yerine, bizim sağladığımız sahte aws komutunu kullanmasını sağlar. Bu sayede:

  • Gerçek AWS hesap kimlik bilgilerine veya ağ bağlantısına ihtiyaç duyulmaz.
  • Testler anında tamamlanır, gerçek bir S3 yüklemesinin bekleme süresi olmaz.
  • Farklı hata senaryolarını (örneğin, aws komutunun bir yetkilendirme hatası döndürmesi) kolayca simüle edebiliriz.

CI/CD ortamında stubbing, geliştiricilerin kodlarını daha hızlı entegre etmelerine, daha güvenilir derlemeler yapmalarına ve nihayetinde daha sağlam yazılımlar sunmalarına olanak tanıyan kritik bir araçtır. Bu sayede, "çalıştı bende" sendromu ortadan kalkar ve otomatik testler, geliştirme yaşam döngüsünün ayrılmaz bir parçası haline gelir. Betikleriniz karmaşıklaştıkça ve dış bağımlılıkları arttıkça, stubbing'in sunduğu faydalar da katlanarak artacaktır.

Sonuç: Shell Betik Testlerinizde Stubbing Neden Olmazsa Olmaz?

Shell betikleri, modern yazılım geliştirme ve sistem yönetimi süreçlerinin vazgeçilmez bir parçasıdır. Ancak, bu betiklerin test edilebilirliği genellikle hafife alınır veya karmaşık bulunur. Bu makale boyunca, 'stubbing sh' tekniğinin, shell betiklerinizi test etme şeklinizi nasıl devrim niteliğinde değiştirebileceğini detaylarıyla ele aldık. Gördüğümüz gibi, stubbing sayesinde dış bağımlılıkların getirdiği karmaşıklık, yavaşlık ve kararsızlık gibi sorunların üstesinden gelmek mümkündür. PATH manipülasyonu ve fonksiyon geçersiz kılma gibi yöntemlerle, betiklerinizin çağırdığı harici komutları ve iç fonksiyonları kendi kontrolümüzdeki sahte versiyonlarıyla değiştirebildik. Bu, bize test ortamı üzerinde tam hakimiyet sağlayarak, öngörülebilir, hızlı ve güvenilir testler yazma imkanı sundu.

Stubbing'in en büyük faydalarından biri, testlerinizin izolasyonunu sağlamasıdır. Gerçek dünya servislerine veya dosya sistemine olan bağımlılıkları ortadan kaldırarak, betiğinizin iş mantığına odaklanmanızı sağlar. Ayrıca, hata senaryolarını kolayca taklit edebilme yeteneği, betiğinizin beklenmedik durumlara karşı ne kadar sağlam olduğunu anlamanıza yardımcı olur. CI/CD ortamlarında ise, stubbing sayesinde testler çok daha hızlı koşar, maliyetler düşer ve dağıtım süreçleri daha güvenilir hale gelir. Bu da sürekli entegrasyon ve dağıtımın temel prensiplerini güçlendirir.

Özetle, shell betiklerinizi bir sonraki seviyeye taşımak ve otomasyon süreçlerinizde tam güven kazanmak istiyorsanız, stubbing vazgeçilmez bir araçtır. Geliştirme yaşam döngünüzün her aşamasında, daha iyi, daha hızlı ve daha güvenilir testler yazmak için bu teknikleri benimsemenizi şiddetle tavsiye ederiz. Unutmayın, test edilmemiş kod, potansiyel bir problem kaynağıdır. Stubbing ile bu riskleri minimize edebilirsiniz.

Sıkça Sorulan Sorular

Stubbing ve Mocking arasındaki temel fark nedir?
Cevap: Stubbing, bir bağımlılığın belirli bir davranışını (örneğin, bir çıktı döndürme) taklit ederken, mocking daha gelişmiş bir tekniktir. Mocking sadece davranış taklidi yapmakla kalmaz, aynı zamanda test edilen birimin bu bağımlılıkla nasıl etkileşim kurduğunu (hangi parametrelerle çağrıldığı, kaç kez çağrıldığı vb.) da doğrular. Shell betiklerinde stubbing genellikle yeterli esnekliği sunar.
Stub'larımı nerede ve nasıl saklamalıyım?
Cevap: Stub'larınızı genellikle test betiklerinizin yanında, özel bir dizinde (örneğin test_stubs/ veya mocks/) saklamanız önerilir. Bu, projenizin yapısını düzenli tutar ve testlerinizi kolayca bulup yönetmenizi sağlar. Her stub betiği, ilgili komutun adıyla (örneğin curl için test_stubs/curl) kaydedilmelidir.
Stubbing güvenlik riski taşır mı?
Cevap: Doğru yapılmadığında evet. Özellikle PATH ortam değişkenini manipüle ederken dikkatli olunmalıdır. Testler bittikten sonra PATH'i orijinal haline getirmek için trap komutunu kullanmak kritik öneme sahiptir. Ayrıca, stub betiklerinizi sadece güvenilir kaynaklardan almalı ve potansiyel kötü niyetli kod içermediğinden emin olmalısınız. CI/CD ortamlarında, testlerin izole konteynerlerde çalışması ek bir güvenlik katmanı sağlar.
Sadece bash için mi geçerli bu teknikler, yoksa diğer kabuklarda da kullanabilir miyim?
Cevap: Bu makalede gösterilen PATH manipülasyonu ve fonksiyon geçersiz kılma gibi temel stubbing teknikleri, sh uyumlu tüm kabuklarda (bash, zsh, ksh gibi) benzer şekillerde çalışır. Sentaks farklılıkları olabilir (örneğin local anahtar kelimesi sh'de bulunmazken bash'te vardır), ancak temel prensipler aynıdır. Bu sayede, farklı kabuk ortamlarında da betiklerinizi güvenle test edebilirsiniz.
Tüm shell betiklerini stub'lamalı mıyım?
Cevap: Hayır, tüm betikleri veya tüm bağımlılıkları stub'lamak zorunda değilsiniz. Stubbing, genellikle yavaş, kararsız, pahalı veya yan etkilere neden olabilecek dış bağımlılıklar için kullanılır. Basit, izole edilmiş ve dış bağımlılığı olmayan betikler doğrudan test edilebilir. Stubbing'i, testlerinizin verimliliğini ve güvenilirliğini en çok artıracağı yerlerde stratejik olarak kullanmalısınız.

Yorumlar
İçeriği beğendiniz mi? Bir tartışma başlatın veya görüşlerinizi paylaşın.
Yorum Yaz

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

Gönder

E-posta Bülteni
Yazılım Topluluğuna Katılın
En son güncellemeleri, yaratıcı ipuçlarını ve özel kaynakları doğrudan e-posta kutunuza alın. Tasarım ve inovasyonun geleceğini birlikte keşfedelim.
Mobil sürümden çık