İçeriğe geç
academia.sh

Ders 14 / 19

Sürekli Tümleştirme

Bir otomatik hattın temiz makinede eksik bir derleme ayarı yüzünden iki sürümde de kaldığının, ayar düzelince test adımının kusuru ayırdığının ve koşmayan adımların kategoriye girmediğinin ölçümü.

Yayın Güncelleme

Bu derste

  • Sürekli tümleştirme hattının adımlarını ve ilk kalan adımda durma kuralını modeller
  • Temiz makinede eksik bir ayarın hattı iki sürümde de kırdığını ve denetimi gürültülü yaptığını ölçer
  • Ayar düzelince test adımının kusurlu sürümü ayırdığını doğrular
  • Koşmayan adımların neden kategoriye girmeyip ayrı sayıldığını açıklar
İçindekiler

Bir değişiklik her yapıldığında aynı adımların aynı sırayla, kimsenin makinesine bağlı olmadan koşması fikrine sürekli tümleştirme (continuous integration) denir. Adımlar bir hat olarak yazılır: önce derle, sonra test et, sonra paketle. Hat bir değişiklikte kalırsa değişiklik ekibe ulaşmadan durur. Bu, bir önceki dersin imza denetimlerini de içine alan bir çatıdır; imza adımı hattın sonunda yer alabilir ve bu derste hat modelinin dışında kalır.

Kursun ölçü adı ayrımdır: aynı denetimi doğru sürüme ve kusurlu sürüme uygulayınca iki yanıt farklı mı. Dört kategori vardır: doğru sürümün geçip kusurlu sürümün kaldığı ayıran, ikisinin de geçtiği kör, ikisinin de kaldığı gürültülü ve doğru sürümün kalıp kusurlu sürümün geçtiği ters. Çıktıda bunlar ayiran, kor, gurultulu ve ters olarak görünür. Bu ders çekirdek dosyayla ölçer.

// Separation.swift — bir denetimin dogru surumu kusurlu surumden ayirip ayirmadigini siniflandirir
struct Contrast<Output: CustomStringConvertible> {
    let label: String
    let correct: Output
    let faulty: Output
    let passesCorrect: Bool
    let passesFaulty: Bool

    var separation: String {
        switch (passesCorrect, passesFaulty) {
        case (true, false): return "ayiran"
        case (true, true): return "kor"
        case (false, false): return "gurultulu"
        case (false, true): return "ters"
        }
    }

    func describe() -> String {
        let onCorrect = passesCorrect ? "gecti" : "kaldi"
        let onFaulty = passesFaulty ? "gecti" : "kaldi"
        print("\(label) | dogru=\(correct)/\(onCorrect) kusurlu=\(faulty)/\(onFaulty) -> \(separation)")
        return separation
    }
}

func contrast<Output>(_ label: String, correct: Output, faulty: Output, check: (Output) -> Bool) -> Contrast<Output> {
    Contrast(label: label, correct: correct, faulty: faulty, passesCorrect: check(correct), passesFaulty: check(faulty))
}

func countSeparations(_ results: [String]) {
    for name in ["ayiran", "kor", "gurultulu", "ters"] {
        print("\(name): \(results.filter { $0 == name }.count)")
    }
}

Bu dersin kusuru sepet toplamındadır. Üç kalemlik bir sepetin toplamı hesaplanırken son kalem sessizce atlanır: doğru sürüm kalemlerin hepsini toplar, kusurlu sürüm sonuncuyu dışarıda bırakır. Hesap hata vermez, çökmez, yalnızca eksik bir sayı döndürür. Bu tür bir kusuru bulan şey çoğunlukla bir testtir ve test ancak çalışırsa bulur. Soru, hattın testi gerçekten çalıştırıp çalıştırmadığıdır.

Hattın Modeli

Model bir hattı adım adım koşturur. Her adım üç sonuçtan birini alır: gecti, kaldi ya da kosmadi. Hat adımları sırayla dener; ilk kaldi alan adımda durur ve kalan adımların hepsi kosmadi olarak işaretlenir. Bu, gerçek hatlardaki davranışın karşılığıdır: derleme kalmışsa test koşturmanın anlamı yoktur ve hat zaman harcamaz.

Üç adım vardır. derle adımı, hedefin gerekli ayarına bakar; ayar yoksa derleme kalır. test adımı, sepet toplamını beklenen değerle karşılaştırır. paketle adımı, toplamın sıfırdan büyük olduğuna bakar ve burada yalnızca hattın son halkası olarak durur. Ayarlar Settings tipindedir; tek alanı hedef ayarının bulunup bulunmadığını söyler.

// Pipeline.swift — adimlari sirayla kosturan ve ilk kalan adimda duran hat modeli
struct Settings {
    let hasTargetSetting: Bool
}

let pipelineSteps = ["derle", "test", "paketle"]

func runStep(_ name: String, total: Int, expected: Int, settings: Settings) -> Bool {
    switch name {
    case "derle": return settings.hasTargetSetting
    case "test": return total == expected
    case "paketle": return total > 0
    default: return false
    }
}

func runPipeline(total: Int, expected: Int, settings: Settings, steps: [String] = pipelineSteps) -> [String] {
    var outcomes: [String] = []
    var stopped = false
    for name in steps {
        if stopped {
            outcomes.append("\(name):kosmadi")
        } else if runStep(name, total: total, expected: expected, settings: settings) {
            outcomes.append("\(name):gecti")
        } else {
            outcomes.append("\(name):kaldi")
            stopped = true
        }
    }
    return outcomes
}

func outcome(of step: String, in outcomes: [String]) -> String {
    for entry in outcomes where entry.hasPrefix(step + ":") {
        return String(entry.dropFirst(step.count + 1))
    }
    return "yok"
}

func trace(_ outcomes: [String]) -> String {
    outcomes.joined(separator: " ")
}

func stepContrast(_ label: String, step: String, correct: [String], faulty: [String]) -> String {
    let onCorrect = outcome(of: step, in: correct)
    let onFaulty = outcome(of: step, in: faulty)
    if onCorrect == "kosmadi" || onFaulty == "kosmadi" {
        print("\(label) | dogru=\(onCorrect) kusurlu=\(onFaulty) -> kategori-disi")
        return "kategori-disi"
    }
    return contrast(label, correct: onCorrect, faulty: onFaulty) { $0 == "gecti" }.describe()
}

func countOutside(_ results: [String]) {
    print("kategori-disi: \(results.filter { $0 == "kategori-disi" }.count)")
}

stepContrast tek bir adımın sonucunu iki sürüm için karşılaştırır, ama adım iki sürümden birinde koşmamışsa sınıflandırmaz. Neden böyle yapıldığı ayrı bir bölümde açıklanıyor; şimdilik yalnızca çıktıda kategori-disi yazdığını bilmek yeter. Bu dersin kuralıdır: ayrımın dört kategorisi çekirdekte durur, kategori-disi ise yalnız bu derste adım koşmadığında yazılır.

Sepet ve beklenen değer ayrı bir dosyada durur. Doğru işlev kalemlerin hepsini toplar, kusurlu işlev sonuncuyu atlar. Beklenen toplam doğru sürümün değeridir ve test adımı onu bu değerden alır.

// Basket.swift — dersin sepeti ve iki toplam islevi
let basketPrices = [40, 25, 15]
let expectedTotal = 80

func basketTotal(_ prices: [Int]) -> Int {
    prices.reduce(0, +)
}

func faultyBasketTotal(_ prices: [Int]) -> Int {
    prices.dropLast().reduce(0, +)
}

DG4. Bu ölçüm hattı üç adımla ve sıralı koşan tek bir iş olarak modeller. Gerçek hatlardaki paralel adımların ve bağımlılık çizgesinin davranışı ölçülmedi.

Temiz Makinede Hat

Hat ilk kez temiz bir makinede koşuyor. Temiz makine, geliştiricinin makinesinde birikmiş ayarları ve yerel önbelleği taşımayan ortamdır. Geliştiricinin makinesinde derleme ayarı bir kez yapılmış ve unutulmuştur; hat tanımına hiç yazılmamıştır. Temiz makinede o ayar yoktur.

DG5. Bu ölçüm temiz ortamın yerel önbelleği ve geliştirici ayarlarını taşımadığını varsayar. Gerçek bir hat ortamının hangi ayarları kendiliğinden getirdiği ölçülmedi.

Hat iki sürüme uygulanır ve üç denetim koşar: hattın bütünüyle yeşil olması, derleme adımının geçmesi ve test adımının geçmesi.

// main.swift — temiz makinede uc denetim
let cleanMachine = Settings(hasTargetSetting: false)
let correctRun = runPipeline(total: basketTotal(basketPrices), expected: expectedTotal, settings: cleanMachine)
let faultyRun = runPipeline(total: faultyBasketTotal(basketPrices), expected: expectedTotal, settings: cleanMachine)

let allGreen = contrast("hat-yesil", correct: trace(correctRun), faulty: trace(faultyRun)) { !$0.contains("kaldi") && !$0.contains("kosmadi") }.describe()
let buildStep = stepContrast("derleme-adimi-gecti", step: "derle", correct: correctRun, faulty: faultyRun)
let testStep = stepContrast("test-adimi-gecti", step: "test", correct: correctRun, faulty: faultyRun)

countSeparations([allGreen, buildStep, testStep])
countOutside([allGreen, buildStep, testStep])
hat-yesil | dogru=derle:kaldi test:kosmadi paketle:kosmadi/kaldi kusurlu=derle:kaldi test:kosmadi paketle:kosmadi/kaldi -> gurultulu
derleme-adimi-gecti | dogru=kaldi/kaldi kusurlu=kaldi/kaldi -> gurultulu
test-adimi-gecti | dogru=kosmadi kusurlu=kosmadi -> kategori-disi
ayiran: 0
kor: 0
gurultulu: 2
ters: 0
kategori-disi: 1

İki sürümde de hat aynı yerde kalır: derle:kaldi. Hattın bütününe bakan hat-yesil denetimi iki sürümde de kalır, yani gürültülü. Derleme adımının kendisine bakan denetim de aynı: iki sürümde kaldi. Hat kırmızıdır ve kusurun bunda payı yoktur; sepet toplamı hiç hesaplanmadan eksik bir ayar hattı durdurmuştur.

Bunun pratik sonucu zararlıdır. Hat kırmızı olunca ekip önce kırmızıyı onarır, çünkü hat kırmızıyken hiçbir değişiklik ilerleyemez. Kırmızının nedeni ayardır ve onarım ayarı eklemektir. Ama ayar eklenene kadar geçen sürede, hattın kusuru görüp görmediğini kimse bilmez; hat kusura hiç ulaşmamıştır. Kırmızı bir hat “kusur yakalandı” diye okunmaya çok yatkındır, ölçüm ise tersini söyler: kusurun yakalanıp yakalanmadığı bu çalıştırmadan çıkmaz.

Üçüncü satır, test-adimi-gecti, sınıflandırılmadı. Test adımı iki sürümde de koşmadı, çünkü hat ondan önce durdu. Çıktıda dogru=kosmadi kusurlu=kosmadi yazar ve sonuç kategori-disi olur. Bu satır ne ayıran, ne kör, ne gürültülü, ne de tersidir; çünkü denetim hiçbir sürümde çalışmamıştır ve çalışmayan bir denetimin iki sürüme verdiği yanıt yoktur.

Ayar Düzelince

Ayar hat tanımına eklenir, temiz makine artık derlemeyi geçer. Aynı üç denetim, bir de paketleme adımına bakan dördüncü denetim, aynı iki sürüme uygulanır.

// main.swift — ayar duzeldikten sonra dort denetim
let fixedMachine = Settings(hasTargetSetting: true)
let correctRun = runPipeline(total: basketTotal(basketPrices), expected: expectedTotal, settings: fixedMachine)
let faultyRun = runPipeline(total: faultyBasketTotal(basketPrices), expected: expectedTotal, settings: fixedMachine)

let allGreen = contrast("hat-yesil", correct: trace(correctRun), faulty: trace(faultyRun)) { !$0.contains("kaldi") && !$0.contains("kosmadi") }.describe()
let buildStep = stepContrast("derleme-adimi-gecti", step: "derle", correct: correctRun, faulty: faultyRun)
let testStep = stepContrast("test-adimi-gecti", step: "test", correct: correctRun, faulty: faultyRun)
let packageStep = stepContrast("paketleme-adimi-gecti", step: "paketle", correct: correctRun, faulty: faultyRun)

countSeparations([allGreen, buildStep, testStep, packageStep])
countOutside([allGreen, buildStep, testStep, packageStep])
hat-yesil | dogru=derle:gecti test:gecti paketle:gecti/gecti kusurlu=derle:gecti test:kaldi paketle:kosmadi/kaldi -> ayiran
derleme-adimi-gecti | dogru=gecti/gecti kusurlu=gecti/gecti -> kor
test-adimi-gecti | dogru=gecti/gecti kusurlu=kaldi/kaldi -> ayiran
paketleme-adimi-gecti | dogru=gecti kusurlu=kosmadi -> kategori-disi
ayiran: 2
kor: 1
gurultulu: 0
ters: 0
kategori-disi: 1

Ayar düzeldiğinde tablo değişir. test-adimi-gecti doğru sürümde geçer, kusurlu sürümde kalır: ayıran. Hattın bütününe bakan hat-yesil de ayırandır, çünkü test kalınca hat kırmızıdır. derleme-adimi-gecti iki sürümde de geçer, kör: kusur derlemeye dokunmaz, derleme her iki sürümde de temiz biter. Dördüncü satır yine kategori-disi.

İlk ölçümle ikincisi arasındaki fark yalnız bir ayardır. Kod aynı, sepet aynı, test aynı; ama hat-yesil denetimi gürültülüden ayırana geçti. Bu, ayrımın denetimin ya da kodun değil, ortamın da bir işlevi olduğunu gösterir. Aynı hat, ayar eksik olduğu sürece, kusur hakkında hiçbir şey söylemez.

Koşmayan Adım Neden Kategori Değildir

Dördüncü satıra dönelim: paketleme-adimi-gecti. Doğru sürümde paketleme geçti, kusurlu sürümde koşmadı. Çekirdek bu çifti, gecti ve kosmadi, doğrudan sınıflandırsaydı denetimi ayıran sayardı: doğru sürüm geçti, kusurlu sürüm “geçemedi”. Ama paketleme adımı kusuru hiç görmemiştir. Kusurlu sürümde sepet toplamı eksiktir ve paketle adımının total > 0 denetimi eksik toplamla da geçerdi; adım yalnız hat testte durduğu için koşmamıştır.

Bu fark önemlidir, çünkü aksi hâlde hattın sonundaki her adım ayıran görünürdü. Paketleme, imzalama, yükleme, her biri hattın ilk kalan adımından sonra yer aldığı için, kusur hat boyunca yakalandığında ayıran sayılırdı. Oysa ayırmak, bir denetimin kusurlu sürüme kendi başına farklı yanıt vermesidir. Koşmayan adımın yanıtı yoktur; o yanıtı hattın ilk kalan adımı belirler.

Dersin yaptığı ayrım şudur. İki sürümde de koştuysa sonuç dört kategoriden birine girer. Birinde koşmadıysa kategori-disi yazılır ve ayrı sayılır. Çıktının son satırı bu sayıyı verir; böylece “kaç denetim sınıflandırıldı” ile “kaç denetim hiç koşmadı” birbirine karışmaz. Yeşil bir hattın kırk adımının otuzu koşmadıysa, o hat otuz denetim kadar sessizdir.

Hat Tanımı

Hat tanımı bir yapılandırma dosyasıdır ve genellikle depoda kod gibi durur. Aşağıdaki gösterim genel bir biçimdir; bir hizmetin gerçek anahtar adları değildir ve bu ders hat tanımını ölçmez. Ölçülen şey hat modelidir.

steps:
  - name: derle
    run: xcodebuild build -scheme Shop
  - name: test
    run: xcodebuild test -scheme Shop
  - name: paketle
    run: xcodebuild archive -scheme Shop

Tanım üç satırlık bir liste gibi görünür ve modelin pipelineSteps dizisiyle birebir örtüşür. Önemli olan, listenin sırası ve her adımın başarısızlıkta hattı durdurup durdurmadığıdır. Çoğu hat tanımı bunu varsayılan olarak yapar; bazıları bir adımın kalmasına rağmen devam edecek biçimde ayarlanabilir. Devam eden bir hatta kosmadi yoktur ve her adımın iki sürüme ne dediği görülür. Bu bir ödündür: hat daha uzun sürer ve daha çok çıktı üretir, ama kategori-disi satırları sınıflandırılabilir satırlara dönüşür.

Hat tanımında bir ayar eksikse, eksiklik ancak ilk temiz makinede görünür. Bu yüzden hattın ilk çalıştırmasında, bilerek kusurlu bir sürümle de çalıştırması iyi bir alışkanlıktır: hat gerçekten test adımına ulaşabiliyor mu, ulaşıyorsa kusurlu sürümde kalıyor mu. Bu tek deneme hat yeşilken sonuçsuz kalan bir denetim sorununu hat kurulurken yakalar.

Sınırlayıcı Ölçüm

Ayrım adım sırasına da bağlıdır. Aşağıdaki ölçümde aynı temiz makine, yani eksik ayar, iki farklı sırayla koşar. Birinci sırada derleme önce gelir, ikincisinde test. Sıra değişince test adımına ulaşılır; ama eksik ayar derleme adımını yine kırar. Beşinci denetim ayar düzelince derleme adımına bakar.

// main.swift — ayni makine, farkli adim sirasi, ayni denetim
let cleanMachine = Settings(hasTargetSetting: false)
let fixedMachine = Settings(hasTargetSetting: true)
let testFirst = ["test", "derle", "paketle"]

let correctTotalNow = basketTotal(basketPrices)
let faultyTotalNow = faultyBasketTotal(basketPrices)

let buildFirstClean = (
    runPipeline(total: correctTotalNow, expected: expectedTotal, settings: cleanMachine),
    runPipeline(total: faultyTotalNow, expected: expectedTotal, settings: cleanMachine))
let testFirstClean = (
    runPipeline(total: correctTotalNow, expected: expectedTotal, settings: cleanMachine, steps: testFirst),
    runPipeline(total: faultyTotalNow, expected: expectedTotal, settings: cleanMachine, steps: testFirst))
let buildFirstFixed = (
    runPipeline(total: correctTotalNow, expected: expectedTotal, settings: fixedMachine),
    runPipeline(total: faultyTotalNow, expected: expectedTotal, settings: fixedMachine))

let first = stepContrast("derle-once-test", step: "test", correct: buildFirstClean.0, faulty: buildFirstClean.1)
let second = stepContrast("test-once-test", step: "test", correct: testFirstClean.0, faulty: testFirstClean.1)
let third = stepContrast("ayar-duzgun-test", step: "test", correct: buildFirstFixed.0, faulty: buildFirstFixed.1)
let fourth = contrast("test-once-hat", correct: trace(testFirstClean.0), faulty: trace(testFirstClean.1)) { !$0.contains("kaldi") }.describe()
let fifth = stepContrast("ayar-duzgun-derle", step: "derle", correct: buildFirstFixed.0, faulty: buildFirstFixed.1)

countSeparations([first, second, third, fourth, fifth])
countOutside([first, second, third, fourth, fifth])
derle-once-test | dogru=kosmadi kusurlu=kosmadi -> kategori-disi
test-once-test | dogru=gecti/gecti kusurlu=kaldi/kaldi -> ayiran
ayar-duzgun-test | dogru=gecti/gecti kusurlu=kaldi/kaldi -> ayiran
test-once-hat | dogru=test:gecti derle:kaldi paketle:kosmadi/kaldi kusurlu=test:kaldi derle:kosmadi paketle:kosmadi/kaldi -> gurultulu
ayar-duzgun-derle | dogru=gecti/gecti kusurlu=gecti/gecti -> kor
ayiran: 2
kor: 1
gurultulu: 1
ters: 0
kategori-disi: 1

Aynı test adımı denetimi, aynı kusur, üç ayrı yanıt: derle-once-test sınıflandırılmaz, test-once-test ve ayar-duzgun-test ayırır. Eksik ayar durduğu sürece test adımının görülüp görülmediği tamamen adım sırasına bağlıdır. Test önce koşan hat, ayar eksik olsa bile kusuru yakalar. Ama hattın bütününe bakan test-once-hat yine de gürültülü kalır: derleme sonraki adımda kırılır ve hat iki sürümde de kırmızıdır. Testin kusuru yakaladığını görmek için hat sonucuna değil, adımın kendi sonucuna bakmak gerekir.

Bu iki gözlem birbirine yaslanır. Sıra, ayrımı belirler; ama sırayı ayrım için seçmek kötü bir fikirdir, çünkü hat derleme olmadan test yapamaz. Gerçek bir uygulamada test, derlenmiş çıktıya ihtiyaç duyar ve bu ölçümdeki sıra değişimi yalnız modelde mümkündür. Dersin çıkarımı, hat tanımını yazarken adımın “kusuru görebilecek konumda mı” sorusuyla birlikte “kusura ulaşılabilir mi” sorusunun da sorulması gerektiğidir.

DG6. Bu ölçüm sıra değişimini modelde serbest sayar; test adımı derlemeye bağlı olmadan koşar. Gerçek bir hatta test adımının derleme çıktısına olan bağımlılığı ölçülmedi.

Özet

  • Sürekli tümleştirme, değişiklik başına aynı adımları temiz bir ortamda sırayla koşturur ve ilk kalan adımda durur.
  • Temiz makinede eksik bir derleme ayarı hattı iki sürümde de kırar. Hattın bütününe ve derleme adımına bakan denetimler gürültülü olur.
  • Ayar düzelince test adımı doğru sürümde geçer, kusurlu sürümde kalır: ayıran. Derleme adımı iki sürümde de geçer: kör.
  • Hat durduktan sonra koşmayan adımlar sınıflandırılmaz; bu derste ayrı sayılır ve kategori-disi yazılır.
  • Ayrım yalnız denetime değil, ortama ve adımların sırasına da bağlıdır; kırmızı bir hat kusurun yakalandığı anlamına gelmez.
  • Bu dersin ayrımı: gürültülü.

Sonraki Adım

Test adımı kusuru yakalayan hat, bir sürümü mağazaya yükleyecek adımlara da ulaşır. Sıradaki derste, Yayın Otomasyonu, sürüm ve derleme numaralarını artıran yükleme betiğinin hangi denetimlerle ayrıldığı ölçülecek.

İlerlemeni kaydetmek ve not almak için Giriş yap

Notlarım

Not almak için giriş yapmalısın.

Aramak için yazmaya başlayın.

↑↓ ↵ Esc gezin · aç · kapat