# Bazel 6: Testlar — ios_unit_test, XCTest va Swift Testing, test cache, coverage va UI testlar

> **Holat:** 2026-yil oktabr. Bu darsda Bazel bilan iOS testlarini yozamiz va ishga tushiramiz: `ios_unit_test`, XCTest va Swift Testing, test natijasi keshi, coverage va UI testlar. Kod misollari Bazel 9.3.0 (Bazelisk 1.29.0), rules_apple 5.3.0, rules_swift 4.2.1 va Xcode 27.1 bilan Apple M4 Pro'da (12 yadro, 24 GB) tekshirilgan.

> **Bu darsdan keyin siz:** iOS kutubxona va ilovasi uchun to'liq test to'plamini Bazel'da qura olasiz — logic testlardan tortib UI testlargacha — va Bazel'ning test keshidan foydalanib, o'zgarmagan testlarni qayta ishlatmay, sekundlarni tejaysiz.

- `swift_library` + `ios_unit_test` bilan test target'ini to'g'ri tuzish (testonly qoidasi bilan)
- XCTest va Swift Testing'ni bitta target'da yozish va ishga tushirish
- `bazel test`, `--test_output`, `--test_filter` — kundalik buyruqlar
- Test natijasi keshi: `(cached) PASSED` — qachon ishlaydi, qachon buziladi
- `size`, `timeout`, `flaky`, `--runs_per_test`, `shard_count` — mo'rt (flaky) testlarni boshqarish
- Logic test, hosted test (`test_host`) va UI test (`ios_ui_test`) — farqi, tezligi, qachon qaysi biri
- `bazel coverage` bilan kod qamrovini (code coverage) o'lchash

## Mundarija

1. [Nega testlar uchun alohida dars?](#nega-test-dars)
2. [Test anatomiyasi: uch rule](#anatomiya)
3. [Birinchi test: XCTest bilan](#birinchi-test)
4. [Swift Testing: zamonaviy sintaksis](#swift-testing)
5. [Testlarni ishga tushirish va filtrlash](#ishga-tushirish)
6. [Test natijasi keshi — Bazel'ning super-kuchi](#test-kesh)
7. [size, timeout va flaky testlar](#size-flaky)
8. [Simulyatorni tanlash va Xcode 27 tuzog'i](#simulyator)
9. [Logic test va hosted test (test_host)](#hosted)
10. [UI testlar va bir timeout hikoyasi](#ui-test)
11. [Coverage — kod qamrovi](#coverage)
12. [ios_build_test — "shunchaki quriladimi?"](#build-test)
13. [Katta jamoalarda test strategiyasi](#strategiya)
14. [Amaliy mashqlar](#mashqlar)
15. [O'zingizni tekshiring](#checklist)
16. [Xulosa](#xulosa)
17. [Manbalar](#manbalar)

---

## 1. Nega testlar uchun alohida dars? {#nega-test-dars}

[5-darsda](/ios-lessons/bazel-05-ios-ilova) biz Bazel bilan birinchi iOS ilovamizni qurdik: `swift_library` kodni kompilyatsiya qiladi, `ios_application` uni `.app`/`.ipa`'ga yig'adi, `bazel run` esa simulyatorda ishga tushiradi. Kod yozdik — endi uning **to'g'ri ishlashini** tekshirishimiz kerak.

Xcode'da testni bilasiz: `Cmd+U` bosasiz, Xcode butun test schema'sini qayta quradi va hammasini ishga tushiradi. Loyiha o'sgani sari bu sekinlashadi. Siz bitta modulning bitta funksiyasini o'zgartirasiz — Xcode esa hech aloqasi yo'q 200 ta boshqa test faylini ham qayta kompilyatsiya qilib, hammasini qayta ishga tushirishi mumkin. CI'da bu yanada og'ir: har PR'da to'liq test to'plami noldan quriladi.

Bazel bu yerda ikki narsani boshqacha qiladi — va ikkalasi ham shu darsning markazida:

1. **Test — bu oddiy `build` target emas, balki alohida rule.** `ios_unit_test` nafaqat test `.xctest` bundle'ini quradi, balki uni simulyatorda ishga tushiradi va natijani (passed/failed) qaytaradi. Bu natija ham, boshqa har qanday action'ning chiqishi kabi, **keshlanadi.**
2. **O'zgarmagan test qayta ishlamaydi.** Agar test binary va uning barcha kirishlari (inputlar) o'zgarmagan bo'lsa, Bazel testni simulyatorda qaytadan ishga tushirmaydi — oldingi natijani keshdan oladi va `(cached) PASSED` deb yozadi. Bu bir zumda bo'ladi.

> **🚀 Motivatsiya:** Keyinroq o'z ko'zingiz bilan ko'rasiz: bizning test to'plamimiz birinchi marta **1.8 soniyada** o'tdi; hech narsa o'zgarmaganda ikkinchi marta ishga tushirsak, Bazel uni umuman ishga tushirmay, **0.19 soniyada** `(cached) PASSED` dedi. Minglab testli katta loyihada bu farq — har kuni yo'qotiladigan yoki tejaladigan soatlar demakdir. Spotify Bazel'ga o'tgach, iOS'da birlashtirishdan oldingi (pre-merge) kutish vaqti **"to'rt barobar"** qisqargan (InfoQ, 2023-yil oktabr) — buning katta qismi aynan shu keshlangan, faqat o'zgarganini qayta ishlaydigan test modelidan.

Shu darsda biz kichik, lekin real test qilsa bo'ladigan loyiha quramiz: **"Kutubxona"** — kitoblar ro'yxatini boshqaradigan oddiy ilova. Uning mantig'i (`KutubxonaKit`) toza Swift kodida bo'ladi, shuning uchun uni tez va mustaqil test qilish mumkin. Keyin ilovaning o'ziga hosted test va UI test yozamiz.

---

## 2. Test anatomiyasi: uch rule {#anatomiya}

Birinchi tuzoq ko'pchilikni shu yerda kutadi: Bazel'da test kodini **asosiy kod bilan bir `swift_library` ichiga qo'shib bo'lmaydi.** Test kodi `XCTest` yoki `Testing` framework'iga bog'liq, bu framework'lar esa faqat test kontekstida mavjud. Shuning uchun struktura doim uch qismdan iborat bo'ladi:

![Test target anatomiyasi: asosiy kod swift_library, test kodi testonly swift_library, ios_unit_test ularni .xctest bundle'ga yig'adi va simulyatorda ishga tushiradi](/lessons/images/bazel/bazel_06_test_anatomy.svg)

Yuqoridagi rasm uch rule'ni va ular hosil qilgan quvurni ko'rsatadi: ikkita `swift_library` (asosiy kod + test kodi) `deps` orqali bog'lanadi, `ios_unit_test` esa test kodidan `.xctest` bundle quradi va uni `ios_xctestrun_runner` orqali simulyatorda ishga tushiradi.

1. **Asosiy kod** — oddiy `swift_library` (test haqida hech narsa bilmaydi).
2. **Test kodi** — alohida `swift_library`, lekin `testonly = True` bayrog'i bilan va asosiy kodga `deps` orqali bog'langan.
3. **Test target** — `ios_unit_test`, u test kutubxonasidan `.xctest` bundle quradi va ishga tushiradi.

`testonly = True` nima qiladi? U Bazel'ga: "bu target faqat testlar uchun; uni ishlab chiqarish (production) kodiga `deps` qilib qo'yib bo'lmaydi" deb aytadi. Agar kimdir xato bilan test kutubxonasini `ios_application`ning `deps`iga qo'shsa, Bazel buildni rad etadi. Bu — arxitekturaviy himoya: test yordamchi kodingiz hech qachon ilovaga sizib kirmaydi.

> **🧠 Esda tuting:** "Nega test kodini alohida `swift_library`ga ajratamiz?" degan savolga aniq javob: `XCTest`/`Testing` import'lari faqat test target'ida mavjud; `testonly` esa ularni production grafidan ajratib turadi. Asosiy kod → test kodi → `ios_unit_test` — bu uchlik har bir modul uchun takrorlanadi (keyingi darslarda uni bitta macro'ga yig'amiz).

Loyihamizning papka tuzilishi:

```text
kutubxona/
├── .bazelversion
├── .bazelrc
├── MODULE.bazel
├── BUILD.bazel              # bo'sh — workspace ildizini package qiladi
├── KutubxonaKit/
│   ├── BUILD.bazel
│   ├── Sources/
│   │   ├── Book.swift
│   │   └── BookLibrary.swift
│   └── Tests/
│       ├── BookTests.swift            # XCTest
│       └── BookLibrarySwiftTests.swift # Swift Testing
└── App/
    ├── BUILD.bazel
    ├── Info.plist
    ├── Sources/
    │   ├── KutubxonaApp.swift
    │   └── ContentView.swift
    ├── HostedTests/
    │   └── HostedTests.swift
    └── UITests/
        └── KutubxonaUITests.swift
```

`MODULE.bazel` — 5-darsdagidek, uchta dependency (apple_support eng tepada turishi kerak, chunki birinchi ro'yxatdan o'tgan cc toolchain yutadi):

```starlark
# MODULE.bazel
module(name = "kutubxona", version = "1.0.0")

bazel_dep(name = "apple_support", version = "2.10.1")
bazel_dep(name = "rules_swift", version = "4.2.1")
bazel_dep(name = "rules_apple", version = "5.3.0")
```

---

## 3. Birinchi test: XCTest bilan {#birinchi-test}

### Sinaladigan kod

Avval test qiladigan narsamiz bo'lsin. `KutubxonaKit` ichida ikkita fayl: `Book` modeli va `BookLibrary` — kitoblarni qo'shish, qidirish, saralash mantig'i. Diqqat qiling: bu kod **toza** — faqat `Foundation`'ga bog'liq, UIKit/SwiftUI yo'q. Aynan shuning uchun uni eng tez test turida (logic test) sinash mumkin.

```swift
// KutubxonaKit/Sources/Book.swift
import Foundation

// Bitta kitob. Barcha maydonlar o'zgarmas (immutable) — test qilish osonroq.
public struct Book: Equatable, Identifiable, Sendable {
    public let id: Int
    public let title: String
    public let author: String
    public let year: Int
    public let pages: Int
    public let isRead: Bool

    public init(
        id: Int,
        title: String,
        author: String,
        year: Int,
        pages: Int,
        isRead: Bool = false
    ) {
        self.id = id
        self.title = title
        self.author = author
        self.year = year
        self.pages = pages
        self.isRead = isRead
    }
}
```

```swift
// KutubxonaKit/Sources/BookLibrary.swift
import Foundation

// Kitoblarni qo'shish, qidirish, saralash logikasi. Bu — toza (pure) logika:
// Foundation'dan tashqari hech narsaga bog'liq emas.
public struct BookLibrary: Sendable {
    public private(set) var books: [Book]

    public init(books: [Book] = []) {
        self.books = books
    }

    public enum LibraryError: Error, Equatable {
        case duplicateID(Int)
        case emptyTitle
    }

    // Validatsiya bilan qo'shish: bir xil id yoki bo'sh sarlavha — xato.
    public mutating func add(_ book: Book) throws {
        guard !book.title.trimmingCharacters(in: .whitespaces).isEmpty else {
            throw LibraryError.emptyTitle
        }
        guard !books.contains(where: { $0.id == book.id }) else {
            throw LibraryError.duplicateID(book.id)
        }
        books.append(book)
    }

    // Sarlavha yoki muallif bo'yicha qidirish (katta-kichik harf farqsiz).
    public func search(_ query: String) -> [Book] {
        let needle = query.trimmingCharacters(in: .whitespaces).lowercased()
        guard !needle.isEmpty else { return books }
        return books.filter {
            $0.title.lowercased().contains(needle)
                || $0.author.lowercased().contains(needle)
        }
    }

    public func unread() -> [Book] {
        books.filter { !$0.isRead }
    }

    public func sortedByYear(ascending: Bool = true) -> [Book] {
        books.sorted { ascending ? $0.year < $1.year : $0.year > $1.year }
    }

    public var totalPages: Int {
        books.reduce(0) { $0 + $1.pages }
    }
}
```

### Test kodi (XCTest)

Endi XCTest testlarini yozamiz. XCTest — Apple'ning klassik test framework'i: `XCTestCase`'dan meros olasiz, metod nomlari `test` bilan boshlanadi, tekshiruvlar `XCTAssert...` funksiyalari bilan yoziladi.

```swift
// KutubxonaKit/Tests/BookTests.swift
import XCTest
@testable import KutubxonaKit

final class BookLibraryXCTests: XCTestCase {

    private func namuna() -> BookLibrary {
        BookLibrary(books: [
            Book(id: 1, title: "O'tkan kunlar", author: "Abdulla Qodiriy", year: 1925, pages: 368, isRead: true),
            Book(id: 2, title: "Mehrobdan chayon", author: "Abdulla Qodiriy", year: 1929, pages: 320),
            Book(id: 3, title: "Shum bola", author: "G'afur G'ulom", year: 1936, pages: 160),
        ])
    }

    func testTotalPages() {
        XCTAssertEqual(namuna().totalPages, 848)
    }

    func testUnreadFiltrlaydi() {
        let unread = namuna().unread()
        XCTAssertEqual(unread.count, 2)
        XCTAssertFalse(unread.contains { $0.isRead })
    }

    func testSearchMuallifBoyicha() {
        let topildi = namuna().search("qodiriy")
        XCTAssertEqual(topildi.count, 2)
    }

    func testSortedByYear() {
        let yillar = namuna().sortedByYear().map(\.year)
        XCTAssertEqual(yillar, [1925, 1929, 1936])
    }

    func testAddDuplicateIDxatoBeradi() {
        var lib = namuna()
        XCTAssertThrowsError(try lib.add(Book(id: 1, title: "Yana bir", author: "X", year: 2000, pages: 100))) { error in
            XCTAssertEqual(error as? BookLibrary.LibraryError, .duplicateID(1))
        }
    }

    func testAddBoshSarlavhaXatoBeradi() {
        var lib = namuna()
        XCTAssertThrowsError(try lib.add(Book(id: 99, title: "   ", author: "X", year: 2000, pages: 100))) { error in
            XCTAssertEqual(error as? BookLibrary.LibraryError, .emptyTitle)
        }
    }
}
```

`@testable import KutubxonaKit` — bu diqqatga sazovor: `@testable` modulning `internal` darajadagi a'zolariga ham kirish beradi (bizda hammasi `public`, lekin odatda test uchun `internal` kod ham ko'rinishi kerak bo'ladi). Buning ishlashi uchun asosiy kutubxona **debug** rejimida (`-enable-testing` bilan) kompilyatsiya qilinishi kerak — Bazel buni test grafigida avtomatik qiladi.

### BUILD fayli

Mana o'sha "uch rule" amalda:

```starlark
# KutubxonaKit/BUILD.bazel
load("@rules_swift//swift:swift.bzl", "swift_library")
load("@rules_apple//apple:ios.bzl", "ios_unit_test")

# 1. Sinaladigan kod
swift_library(
    name = "KutubxonaKit",
    srcs = glob(["Sources/**/*.swift"]),
    module_name = "KutubxonaKit",
    visibility = ["//visibility:public"],
)

# 2. Test kodi alohida kutubxonada: testonly = True
swift_library(
    name = "KutubxonaKitTestsLib",
    testonly = True,
    srcs = glob(["Tests/**/*.swift"]),
    deps = [":KutubxonaKit"],
)

# 3. Test target'i: .xctest bundle quradi va ishga tushiradi
ios_unit_test(
    name = "KutubxonaKitTests",
    minimum_os_version = "17.0",
    deps = [":KutubxonaKitTestsLib"],
)
```

`ios_unit_test`ning majburiy atributlari kam: `name` va `minimum_os_version`. `deps` — test kutubxonasi. `test_host` bermaganimiz uchun bu **logic test** bo'ladi — ya'ni ilovasiz, mustaqil ishlaydi (bu haqda 9-bo'limda batafsil). Rule'ning to'liq atributlari ro'yxati rules_apple 5.3.0 hujjatlaridagi `ios_unit_test` bo'limida (`deps`, `data`, `env`, `size`, `timeout`, `test_filter`, `test_host`, `shard_count`, `runner`, `collect_code_coverage`...).

Avval shunchaki quramiz (hali ishga tushirmaymiz):

```bash
bazel build //KutubxonaKit:KutubxonaKitTests
```

```text
INFO: Analyzed target //KutubxonaKit:KutubxonaKitTests (200 packages loaded, 7850 targets configured).
[340 / 352] Compiling Swift module //KutubxonaKit:KutubxonaKit; 1s disk-cache, worker
[342 / 352] Compiling Swift module //KutubxonaKit:KutubxonaKitTestsLib; 0s disk-cache, worker
[348 / 352] Bundling KutubxonaKitTests.__internal__.__test_bundle; 0s disk-cache, darwin-sandbox
Target //KutubxonaKit:KutubxonaKitTests up-to-date:
  bazel-bin/KutubxonaKit/KutubxonaKitTests
  bazel-out/ios_sim_arm64-fastbuild-ios-sim_arm64-min17.0-ST-.../bin/KutubxonaKit/KutubxonaKitTests.zip
INFO: Elapsed time: 29.168s, Critical Path: 8.48s
INFO: 352 processes: 174 disk cache hit, 169 internal, 6 darwin-sandbox, 1 local, 2 worker.
```

E'tibor bering: `bazel build` test'ni **qurdi**, lekin ishga tushirmadi — `.xctest` bundle `bazel-bin`'da tayyor turibdi. Testni haqiqatda ishga tushirish uchun `bazel build` emas, `bazel test` kerak. Lekin avval `.bazelrc`'ga bir muhim sozlamani qo'shishimiz kerak, aks holda test simulyator xatosi bilan yiqiladi — buni 8-bo'limda hal qilamiz. Hozircha keling, Swift Testing'ni ko'rib chiqaylik.

---

## 4. Swift Testing: zamonaviy sintaksis {#swift-testing}

XCTest 2015-yildan beri bor va hamma joyda ishlaydi. 2024-yilda Apple **Swift Testing** — yangi, zamonaviy test framework'ini chiqardi: `XCTestCase` merosi yo'q, oddiy `@Test` funksiyalari; `XCTAssertEqual` o'rniga oddiy `#expect(a == b)`; suite'lar oddiy `struct`. Eng muhimi — Bazel bulardan birini tanlashga majbur qilmaydi: **ikkalasini bitta `ios_unit_test`da aralashtirib yozsangiz ham ishlaydi.**

Mana o'sha `BookLibrary` uchun Swift Testing testlari:

```swift
// KutubxonaKit/Tests/BookLibrarySwiftTests.swift
import Testing
@testable import KutubxonaKit

@Suite("BookLibrary — Swift Testing")
struct BookLibrarySwiftTests {

    private func namuna() -> BookLibrary {
        BookLibrary(books: [
            Book(id: 1, title: "O'tkan kunlar", author: "Abdulla Qodiriy", year: 1925, pages: 368, isRead: true),
            Book(id: 2, title: "Mehrobdan chayon", author: "Abdulla Qodiriy", year: 1929, pages: 320),
            Book(id: 3, title: "Shum bola", author: "G'afur G'ulom", year: 1936, pages: 160),
        ])
    }

    @Test("Umumiy sahifalar soni to'g'ri hisoblanadi")
    func totalPages() {
        #expect(namuna().totalPages == 848)
    }

    @Test("O'qilmagan kitoblar faqat isRead == false bo'lganlari")
    func unread() {
        let unread = namuna().unread()
        #expect(unread.count == 2)
        #expect(!unread.contains { $0.isRead })
    }

    @Test("Bo'sh sarlavha .emptyTitle xatosini beradi")
    func emptyTitleThrows() throws {
        var lib = namuna()
        #expect(throws: BookLibrary.LibraryError.emptyTitle) {
            try lib.add(Book(id: 50, title: "  ", author: "X", year: 2001, pages: 50))
        }
    }

    // Parametrlashtirilgan test: bitta funksiya, uchta kirish/natija jufti.
    @Test("Qidiruv bir nechta so'rov uchun to'g'ri soni qaytaradi", arguments: [
        ("qodiriy", 2),
        ("shum", 1),
        ("tolkien", 0),
    ])
    func searchCount(query: String, kutilgan: Int) {
        #expect(namuna().search(query).count == kutilgan)
    }
}
```

Nimalarga e'tibor berish kerak:

- `@Suite("...")` — testlar guruhi (ixtiyoriy nom bilan). Oddiy `struct`, meros yo'q.
- `@Test("...")` — bitta test. Nomi — o'qilishi oson matn.
- `#expect(...)` — tekshiruv. `XCTAssertEqual(a, b)` o'rniga oddiy `#expect(a == b)`. Agar yiqilsa, Swift Testing ifodaning ikki tomonidagi qiymatlarni ko'rsatadi.
- `#expect(throws: ...)` — xato kutilishini tekshiradi (`XCTAssertThrowsError` ekvivalenti).
- `@Test(arguments: [...])` — **parametrlashtirilgan test**: bitta funksiya har bir argument jufti uchun qayta chaqiriladi. Uchta holat uchun uchta alohida test yozish shart emas.

BUILD faylini o'zgartirishning hojati yo'q: `glob(["Tests/**/*.swift"])` yangi faylni ham o'z ichiga oladi. Test kutubxonasi endi ikki faylli: biri XCTest, biri Swift Testing. `ios_unit_test` ikkalasini bitta `.xctest` bundle'ga qo'shadi va **ikkala framework'ni ham ishga tushiradi.**

> **🧠 Esda tuting:** rules_swift Swift Testing'ni tug'ma qo'llaydi — uning ichida `tools/test_observer/SwiftTestingRunner.swift` test observer'i bor. Siz hech qanday qo'shimcha dependency yoki bayroq qo'shishingiz shart emas; faqat `import Testing` yozing. (Bu bizning mashinada rules_swift 4.2.1 + Xcode 27.1 bilan tekshirilgan.)

Natijani 5-bo'limda ishga tushirganimizda ko'rasiz — bitta buyruq ikkala framework summary'sini ham chiqaradi.

---

## 5. Testlarni ishga tushirish va filtrlash {#ishga-tushirish}

`bazel build` test'ni quradi; `bazel test` esa quradi **va** ishga tushiradi. (Avval `.bazelrc`'ga simulyator sozlamasini qo'shgan bo'lishingiz kerak — 8-bo'limga qarang; mana shu misollar o'sha sozlama bilan ishlaydi.)

```bash
bazel test //KutubxonaKit:KutubxonaKitTests --test_output=all
```

`--test_output=all` test'ning to'liq chiqishini ko'rsatadi. Mana haqiqiy natija (qisqartirilgan), avval XCTest, keyin Swift Testing:

```text
Test Suite 'BookLibraryXCTests' started at 2026-10-13 12:42:44.621.
Test Case '-[...BookLibraryXCTests testTotalPages]' passed (0.000 seconds).
Test Case '-[...BookLibraryXCTests testUnreadFiltrlaydi]' passed (0.000 seconds).
...
	 Executed 6 tests, with 0 failures (0 unexpected) in 0.013 (0.014) seconds
◇ Test run started.
↳ Testing Library Version: 2084
↳ Target Platform: arm64-apple-ios17.0-simulator
◇ Suite "BookLibrary — Swift Testing" started.
✔ Test "Umumiy sahifalar soni to'g'ri hisoblanadi" passed after 0.001 seconds.
◇ Test case passing 2 arguments query → "qodiriy", kutilgan → 2 to "Qidiruv ..." started.
✔ Test "Qidiruv bir nechta so'rov uchun to'g'ri soni qaytaradi" with 3 test cases passed after 0.001 seconds.
✔ Suite "BookLibrary — Swift Testing" passed after 0.005 seconds.
✔ Test run with 4 tests in 1 suite passed after 0.005 seconds.
Testing is serialized
//KutubxonaKit:KutubxonaKitTests                                         PASSED in 1.8s

Executed 1 out of 1 test: 1 test passes.
```

Ko'rib turganingizdek, ikkala framework ham ishladi: XCTest "Executed 6 tests", Swift Testing "Test run with 4 tests in 1 suite passed" (parametrlashtirilgan test "with 3 test cases" deb sanaldi). Bazel esa butun `.xctest` bundle'ni **bitta test target** sifatida ko'radi: `PASSED in 1.8s`.

### `--test_output` rejimlari

- `--test_output=summary` (standart) — faqat o'tdi/yiqildi va log fayl manzili.
- `--test_output=errors` — faqat **yiqilgan** testlarning logini chiqaradi. CI uchun ideal: muvaffaqiyatli testlar shovqin qilmaydi.
- `--test_output=all` — hamma narsani chiqaradi (yuqoridagi kabi).
- `--test_output=streamed` — testni real vaqtda, oqim ko'rinishida ko'rsatadi (bitta test, debug uchun; parallel ishlashni o'chiradi).

Odatda `.bazelrc`'ga `test --test_output=errors` yozib qo'yiladi — biz ham shunday qildik.

### Bitta testni ishga tushirish: `--test_filter`

Butun to'plamni emas, bitta metodni ishga tushirmoqchimisiz? `--test_filter` buni qiladi. Format — `KlassNomi/metodNomi`:

```bash
bazel test //KutubxonaKit:KutubxonaKitTests \
  --test_filter=BookLibraryXCTests/testTotalPages \
  --test_output=all --nocache_test_results
```

```text
Test Case '-[...BookLibraryXCTests testTotalPages]' started.
Test Case '-[...BookLibraryXCTests testTotalPages]' passed (0.002 seconds).
	 Executed 1 test, with 0 failures (0 unexpected) in 0.002 (0.004) seconds
//KutubxonaKit:KutubxonaKitTests                                         PASSED in 9.4s
Executed 1 out of 1 test: 1 test passes.
```

Faqat bitta test ishladi ("Executed 1 test"). `--test_filter` regular expression ham qabul qiladi: `--test_filter='BookLibraryXCTests/test(Total|Unread).*'` ikkala metodni ishga tushiradi. Bu filter test runner'ga uzatiladi — ya'ni bundle baribir to'liq quriladi, lekin faqat tanlangan metodlar bajariladi.

> **💡 Pro tip:** `PASSED in 9.4s` — nega bu safar sekin? Chunki `--nocache_test_results` keshni o'chirdi va test simulyatorda yangidan ishga tushdi (simulyator bilan muloqot vaqtni oshiradi). Keshdan kelganda esa, hozir ko'ramiz, bu **0.2 soniya** bo'ladi.

### Test yiqilganda nima bo'ladi?

Keling, ataylab bitta testni buzamiz — `testTotalPages`'da `848` o'rniga `999` kutamiz — va `--test_output=errors` bilan ishga tushiramiz:

```text
BookTests.swift:16: error: -[...BookLibraryXCTests testTotalPages] : XCTAssertEqual failed: ("848") is not equal to ("999")
Test Case '-[...BookLibraryXCTests testTotalPages]' failed (0.053 seconds).
	 Executed 6 tests, with 1 failure (0 unexpected) in 0.056 seconds
//KutubxonaKit:KutubxonaKitTests                                         FAILED in 4.3s

Executed 1 out of 1 test: 1 fails locally.
```

Bazel aniq ko'rsatadi: qaysi fayl va qator (`BookTests.swift:16`), nima kutilgan (`848`), nima bo'lgan (`999`). Target `FAILED` bo'ldi va `bazel test` nol bo'lmagan exit code bilan tugaydi — CI buni to'xtatish uchun ishlatadi.

---

## 6. Test natijasi keshi — Bazel'ning super-kuchi {#test-kesh}

Mana shu — Bazel testlarini boshqa test yurituvchilardan ajratib turadigan asosiy narsa. Esingizdami, Bazel har action uchun **action key** hisoblaydi (3-darsda ko'rgan edik). Test ham action: uning key'i test binary, runner, kirish fayllari va muhim bayroqlardan hisoblanadi. Agar bu key o'zgarmagan bo'lsa, natija ham o'zgarmaydi — demak testni qaytadan ishga tushirishning ma'nosi yo'q.

![Test keshi qarori: action key kirishlar o'zgarmasa, test qayta ishlamaydi va (cached) PASSED beradi; o'zgarsa, simulyatorda ishga tushadi](/lessons/images/bazel/bazel_06_test_cache.svg)

Buni o'z ko'zingiz bilan ko'ring. Birinchi marta ishga tushiramiz:

```bash
bazel test //KutubxonaKit:KutubxonaKitTests
```

```text
//KutubxonaKit:KutubxonaKitTests                                         PASSED in 1.8s
Executed 1 out of 1 test: 1 test passes.
```

Endi hech narsani o'zgartirmay, **yana** ishga tushiramiz:

```bash
bazel test //KutubxonaKit:KutubxonaKitTests
```

```text
INFO: Elapsed time: 0.188s, Critical Path: 0.02s
INFO: 1 process: 1 action cache hit, 1 internal.
//KutubxonaKit:KutubxonaKitTests                                (cached) PASSED in 1.8s

Executed 0 out of 1 test: 1 test passes.
```

Ikki narsaga qarang:

1. **`(cached) PASSED`** — test simulyatorda umuman ishlamadi; Bazel oldingi natijani keshdan oldi.
2. **`Executed 0 out of 1 test`** — nol test bajarildi, lekin baribir "1 test passes". Butun jarayon **0.188 soniya** davom etdi (birinchi martadagi bir necha soniya o'rniga).

> **📊 Real raqamlar:** Bizning kichik to'plamimizda (10 test) birinchi ishga tushish — `PASSED in 1.8s`; keshdan — butun buyruq `0.188s`. Ya'ni kesh testni ~10 barobar tez "o'tkazdi", chunki aslida u umuman ishlamadi. Minglab testli loyihada bu farq soatlab CI vaqtini tejaydi. (O'lchov: M4 Pro, Xcode 27.1, Bazel 9.3.0, iPhone 17 Pro / iOS 27.0.)

### Keshni nima buzadi?

Kesh "jim turib qolgan eski natija"ni bermaydi — u faqat **hech narsa o'zgarmaganda** ishlaydi. Quyidagilardan birortasi test key'ini o'zgartiradi va testni qaytadan ishga tushiradi:

- test faylini yoki **sinaladigan kodni** o'zgartirish (`BookLibrary.swift`ni tahrirlasangiz, test qayta ishlaydi);
- test bayroqlarini o'zgartirish (`--test_filter`, `--test_env`, `--test_arg`);
- test muhiti (`env`), `data` fayllari;
- simulyator yoki Xcode versiyasini o'zgartirish.

### Keshni ataylab chetlab o'tish

Ba'zan testni **majburan** qayta ishga tushirishingiz kerak bo'ladi — masalan, flaky (beqaror) testni ushlash uchun:

```bash
# Har safar qayta ishga tushiradi, keshni mutlaqo e'tiborsiz qoldiradi
bazel test //KutubxonaKit:KutubxonaKitTests --nocache_test_results
```

```text
//KutubxonaKit:KutubxonaKitTests                                         PASSED in 2.9s
Executed 1 out of 1 test: 1 test passes.
```

Endi `(cached)` yo'q va "Executed 1 out of 1" — test haqiqatda qayta ishladi. `--cache_test_results=no` ham xuddi shunday.

> **⚠️ Diqqat:** Test keshi juda foydali, lekin u faqat **hermetik** (yopiq) testlar uchun to'g'ri ishlaydi. Agar testingiz tashqi holatga bog'liq bo'lsa — tarmoq, joriy vaqt (`Date()`), fayl tizimidagi global papka, tasodifiy son — kesh sizni aldashi mumkin: kod o'zgarmagan, lekin natija boshqacha bo'lishi mumkin edi. Testlarni hermetik yozing (kirishlarni in'ektsiya qiling), aks holda `(cached) PASSED` yolg'on xotirjamlik beradi.

---

## 7. size, timeout va flaky testlar {#size-flaky}

Testlarni ishga tushirganingizda shunday ogohlantirish ko'rgandirsiz:

```text
There were tests whose specified size is too big. Use the
--test_verbose_timeout_warnings command line option to see which ones these are.
```

Bu nima? Bazel har test target'iga **`size`** beradi (standart — `medium`). `size` ikki narsani belgilaydi: test qancha resurs talab qiladi va uning **timeout**'i qancha. Bizning testlar 2 soniyada o'tyapti, lekin `medium` size timeout'i 300 soniya — Bazel "sizning testingiz juda katta size'da e'lon qilingan, aslida u juda tez" deb ogohlantiryapti.

To'g'rilaymiz — `ios_unit_test`ga `size = "small"` qo'shamiz:

```starlark
# KutubxonaKit/BUILD.bazel (yangilangan)
ios_unit_test(
    name = "KutubxonaKitTests",
    size = "small",            # tez logic testlar uchun
    minimum_os_version = "17.0",
    deps = [":KutubxonaKitTestsLib"],
)
```

`size` qiymatlari va ularning standart timeout'lari (Bazel hujjatlaridan):

| size       | Standart timeout | Qachon                                    |
| :--------- | :--------------- | :---------------------------------------- |
| `small`    | 60 s (short)     | Tez logic/unit testlar — ko'pchiligi shu  |
| `medium`   | 300 s (moderate) | Simulyator yoki disk bilan ishlaydiganlar |
| `large`    | 900 s (long)     | Og'ir integratsiya/UI testlar             |
| `enormous` | 3600 s (eternal) | Juda kam — butun oqim testlari            |

`timeout`'ni `size`'dan alohida ham berish mumkin: `timeout = "short"` (60 s), `"moderate"` (300 s), `"long"` (900 s), `"eternal"` (3600 s). Qoida: testingizga mos **eng kichik** size'ni tanlang — shunda haqiqatan osilib qolgan test tez aniqlanadi, 15 daqiqa kutib o'tirmaysiz.

### Flaky (beqaror) testlar

Ba'zi testlar ba'zan o'tadi, ba'zan yiqiladi — kod o'zgarmasa ham. Bu **flaky** testlar: odatda vaqt poygasi (race), tartibga bog'liqlik yoki tashqi holat sabab. Bazel ularni boshqarish uchun bir nechta vosita beradi.

**Flaky testni ushlash** — bitta testni ko'p marta ishga tushiring:

```bash
# Bitta testni ketma-ket 20 marta ishga tushiradi; bittasi ham yiqilsa — muammo bor
bazel test //KutubxonaKit:KutubxonaKitTests \
  --test_filter=BookLibraryXCTests/testSortedByYear \
  --runs_per_test=20
```

Agar 20 tadan bittasi yiqilsa, sizda flaky test bor. `--runs_per_test=N` kesh bilan ishlamaydi (har safar haqiqatan ishga tushadi) — bu ataylab shunday.

**Flaky deb belgilash** — vaqtincha chora sifatida, testni `flaky = True` qilib qo'yish mumkin:

```starlark
ios_unit_test(
    name = "KutubxonaKitTests",
    size = "small",
    flaky = True,              # yiqilsa, Bazel 3 martagacha qayta uriadi
    minimum_os_version = "17.0",
    deps = [":KutubxonaKitTestsLib"],
)
```

`flaky = True` bo'lsa, test yiqilganda Bazel uni 3 martagacha qayta uriadi; agar bir marta o'tsa, "PASSED" (lekin "flaky" deb belgilaydi). `--flaky_test_attempts=N` bilan urinishlar sonini o'zgartirasiz.

> **⚠️ Diqqat:** `flaky = True` — bu muammoni **yashirish**, hal qilish emas. U faqat vaqtincha, siz asl sababni (race, tartib, tashqi holat) topguningizcha ishlatiladi. Doimiy `flaky = True` — texnik qarz: beqaror test ertami-kechmi haqiqiy xatoni o'tkazib yuboradi.

### Sharding — bitta test target'ini bo'laklarga bo'lish

Agar bitta target'da yuzlab test bo'lsa, ularni parallel ishga tushirish uchun **sharding** ishlatiladi:

```starlark
ios_unit_test(
    name = "KutubxonaKitTests",
    size = "small",
    shard_count = 4,           # testlarni 4 bo'lakka bo'lib, parallel ishga tushiradi
    minimum_os_version = "17.0",
    deps = [":KutubxonaKitTestsLib"],
)
```

`shard_count = 4` bo'lsa, Bazel test'ni 4 ta shard sifatida ishga tushiradi (har biri testlarning bir qismini oladi), va ular bir-biriga parallel ishlashi mumkin. Bizning 10 testli kichik to'plamimizga bu kerak emas — lekin 500 testli modulda bu sezilarli tezlanish beradi. (`shard_count` — kichik modullar uchun ortiqcha; faqat test soni ko'p bo'lganda qo'shing.)

---

## 8. Simulyatorni tanlash va Xcode 27 tuzog'i {#simulyator}

Endi ochig'ini aytamiz: agar 3-bo'limdagi `ios_unit_test`ni **to'g'ridan-to'g'ri**, hech qanday sozlamasiz ishga tushirsangiz, Xcode 27 muhitida u yiqiladi. Bu — har bir yangi boshlovchini kutib turgan tuzoq, shuning uchun sababini tushunaylik.

`ios_unit_test` standart holda `ios_xctestrun_runner`'dan foydalanadi (rules_apple 5.0'dan beri standart runner shu). Bu runner test uchun simulyator tanlaydi. U qurilmani quyidagi tartibda qidiradi (rules_apple 5.3.0 manbasidan):

1. test target'ining `device_type`/`os_version` atributlari (agar berilgan bo'lsa);
2. `--@rules_apple//apple/build_settings:ios_simulator_device` / `...ios_simulator_version` build sozlamalari;
3. native Bazel bayroqlari `--ios_simulator_device` / `--ios_simulator_version` (manbadagi izoh: "Bazel 9.x qo'llab-quvvatlashni tashlaganimizda olib tashlanadi");
4. hech biri berilmasa — qattiq kodlangan **"iPhone 15"**.

Muammo shundaki, Xcode 27 Apple Silicon'da "iPhone 15" simulyatori yo'q (eng eski oddiy model boshqacha), shuning uchun 4-variant `simctl create ... Incompatible device` xatosi bilan yiqiladi. (Bu bizning mashinamizda tekshirildi.)

Yechim — 2-variantni ishlatish, ya'ni `.bazelrc`'da build sozlamasi orqali o'z simulyatoringizni belgilash. Bu forma eng kelajakka chidamli (native bayroqlar vaqti kelib olib tashlanadi):

```text
# .bazelrc
build --disk_cache=~/.cache/bazel-disk

# Xcode 27.x: standart runner "iPhone 15" so'raydi va yiqiladi.
# O'zimizning simulyatorni build sozlamasi orqali tanlaymiz.
test --@rules_apple//apple/build_settings:ios_simulator_device="iPhone 17 Pro"
test --@rules_apple//apple/build_settings:ios_simulator_version=27.0

test --test_output=errors
```

> **⚠️ Diqqat:** Qaysi simulyatorlar mavjudligini o'zingiz tekshiring — "iPhone 17 Pro" va "27.0" bizning mashinada bor, sizda boshqacha bo'lishi mumkin:
>
> ```bash
> xcrun simctl list devicetypes | grep iPhone
> xcrun simctl list runtimes | grep iOS
> ```
>
> Mos qurilma va versiyani tanlab, `.bazelrc`'ga yozing.

Birinchi marta test ishga tushganda, runner `BAZEL_TEST_iPhone 17 Pro_27.0` nomli doimiy simulyator yaratadi va uni keyingi testlarda qayta ishlatadi (`reuse_simulator` standart holda `True`). Shuning uchun birinchi test sekinroq (simulyator yaratiladi va yuklanadi), keyingilar tezroq.

### Test tartibini tasodifiylashtirish

Agar testlaringiz bir-biriga yashirin bog'liq bo'lsa (biri ikkinchisi qoldirgan holatga tayansa), ular doim bir tartibda ishlagani uchun buni sezmaysiz. `ios_xctestrun_random_runner` testlarni **tasodifiy tartibda** ishga tushiradi va shunday bog'liqliklarni fosh qiladi:

```starlark
ios_unit_test(
    name = "KutubxonaKitTests",
    size = "small",
    minimum_os_version = "17.0",
    runner = "@rules_apple//apple/testing/default_runner:ios_xctestrun_random_runner",
    deps = [":KutubxonaKitTestsLib"],
)
```

Tartibli runner ham bor (`ios_xctestrun_ordered_runner`) — bu standart (tasodifiy emas) xatti-harakat.

---

## 9. Logic test va hosted test (test_host) {#hosted}

Hozirgacha yozgan testlarimiz **logic test** (library test) edi: `test_host` berilmagan, shuning uchun test ilovasiz, mustaqil ishlaydi. Bu eng tez tur. Lekin uning cheklovi bor: ilova konteksti yo'q. `Bundle.main` test bundle'iga ishora qiladi, ilovaga emas; `UIApplication`, ba'zi `NSUserDefaults` xatti-harakatlari mavjud emas.

Ba'zan sizga ilova **ichida** ishlaydigan test kerak — masalan, ilova bundle'idagi resursni o'qiydigan yoki ilova holatiga bog'liq kodni sinaydigan test. Bu — **hosted test**: `test_host` atributiga ilovani berasiz, va test o'sha ilovaning jarayonida (process) ishlaydi.

Avval sinaladigan ilova kerak. `App/` papkasida oddiy SwiftUI ilova (5-darsdagi kabi):

```swift
// App/Sources/KutubxonaApp.swift
import SwiftUI
import KutubxonaKit

@main
struct KutubxonaApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView(library: BookLibrary(books: [
                Book(id: 1, title: "O'tkan kunlar", author: "Abdulla Qodiriy", year: 1925, pages: 368, isRead: true),
                Book(id: 2, title: "Mehrobdan chayon", author: "Abdulla Qodiriy", year: 1929, pages: 320),
                Book(id: 3, title: "Shum bola", author: "G'afur G'ulom", year: 1936, pages: 160),
            ]))
        }
    }
}
```

```swift
// App/Sources/ContentView.swift
import SwiftUI
import KutubxonaKit

struct ContentView: View {
    let library: BookLibrary

    var body: some View {
        NavigationStack {
            List(library.sortedByYear()) { book in
                VStack(alignment: .leading, spacing: 4) {
                    Text(book.title).font(.headline)
                    Text("\(book.author) · \(book.year)")
                        .font(.subheadline)
                        .foregroundStyle(.secondary)
                }
                // UI test shu identifikator orqali qatorni topadi (10-bo'lim)
                .accessibilityIdentifier("book-\(book.id)")
            }
            .navigationTitle("Kutubxona")
        }
    }
}
```

Hosted test — `Bundle.main`ning endi ilova bundle'iga ishora qilishini tekshiramiz:

```swift
// App/HostedTests/HostedTests.swift
import XCTest
@testable import KutubxonaKit

final class HostedTests: XCTestCase {

    func testIlovaBundleIDmavjud() {
        // Logic (test_host'siz) testda bu nil yoki test bundle bo'lardi.
        let bundleID = Bundle.main.bundleIdentifier
        XCTAssertEqual(bundleID, "uz.azimjondev.kutubxona")
    }

    func testKutubxonaKitHostKontekstidaIshlaydi() {
        let lib = BookLibrary(books: [
            Book(id: 1, title: "O'tkan kunlar", author: "Abdulla Qodiriy", year: 1925, pages: 368),
        ])
        XCTAssertEqual(lib.totalPages, 368)
    }
}
```

BUILD fayli — asosiy farq `test_host = ":Kutubxona"`:

```starlark
# App/BUILD.bazel
load("@rules_swift//swift:swift.bzl", "swift_library")
load(
    "@rules_apple//apple:ios.bzl",
    "ios_application",
    "ios_ui_test",
    "ios_unit_test",
)

swift_library(
    name = "AppLib",
    srcs = glob(["Sources/**/*.swift"]),
    deps = ["//KutubxonaKit"],
)

ios_application(
    name = "Kutubxona",
    bundle_id = "uz.azimjondev.kutubxona",
    families = ["iphone"],
    infoplists = ["Info.plist"],
    minimum_os_version = "17.0",
    deps = [":AppLib"],
)

# Hosted unit test: ilova jarayonida ishlaydi
swift_library(
    name = "HostedTestsLib",
    testonly = True,
    srcs = glob(["HostedTests/**/*.swift"]),
    deps = ["//KutubxonaKit"],
)

ios_unit_test(
    name = "HostedTests",
    minimum_os_version = "17.0",
    test_host = ":Kutubxona",     # <-- test ilova ichida ishlaydi
    deps = [":HostedTestsLib"],
)
```

Ishga tushiramiz:

```bash
bazel test //App:HostedTests
```

```text
** TEST EXECUTE SUCCEEDED **
//App:HostedTests                                                        PASSED in 17.7s
Executed 1 out of 1 test: 1 test passes.
```

> **📊 Real raqamlar:** Logic test `PASSED in 1.8s`, hosted test esa `PASSED in 17.7s` — deyarli 10 barobar sekinroq. Sababi: hosted test to'liq ilovani simulyatorda ishga tushiradi va `xcodebuild`'dan foydalanadi (log'da `IDETestOperationsObserverDebug` qatorlari ko'rinadi), logic test esa ilovasiz, to'g'ridan-to'g'ri `xctest` bilan ishlaydi. (O'lchov: M4 Pro, Xcode 27.1, iPhone 17 Pro / iOS 27.0.)

> **💡 Pro tip:** Shuning uchun qoida oddiy: **imkon qadar logic test yozing.** Toza logikani (model, hisob-kitob, validatsiya) `test_host`siz sinang — bu tez va ishonchli. `test_host`ni faqat haqiqatan ilova konteksti kerak bo'lganda qo'shing. Minglab testli loyihada bu farq CI vaqtini belgilaydi.

---

## 10. UI testlar va bir timeout hikoyasi {#ui-test}

UI test — eng yuqori daraja: u ilovani foydalanuvchi kabi boshqaradi (tugmalarni bosadi, matnlarni o'qiydi, ekranlar orasida yuradi). Bazel'da bu `ios_ui_test` rule'i bilan yoziladi va u ham `test_host`ni talab qiladi (boshqariladigan ilova).

Avval ilovamizni test qilish uchun unga belgilar qo'yamiz — `ContentView`'da har qatorga `.accessibilityIdentifier("book-\(book.id)")` qo'ygan edik, va `navigationTitle("Kutubxona")`. UI test shulardan foydalanadi.

Mana bu — **birinchi urinishimiz**, va u mantiqan to'g'ri ko'rinadi:

```swift
// App/UITests/KutubxonaUITests.swift — BIRINCHI URINISH (yiqiladi!)
import XCTest

// UI test: XCUIApplication ilovani simulyatorda ishga tushiradi va ekran
// elementlari bilan foydalanuvchi kabi ishlaydi.
final class KutubxonaUITests: XCTestCase {

    func testRoyxatKitoblarniKorsatadi() {
        let app = XCUIApplication()
        app.launch()

        // ContentView'dagi accessibilityIdentifier orqali qatorni topamiz
        let birinchiKitob = app.cells["book-1"]
        XCTAssertTrue(birinchiKitob.waitForExistence(timeout: 10))
        XCTAssertTrue(app.navigationBars["Kutubxona"].exists)
    }
}
```

```starlark
# App/BUILD.bazel (davomi)
swift_library(
    name = "UITestsLib",
    testonly = True,
    srcs = glob(["UITests/**/*.swift"]),
)

ios_ui_test(
    name = "KutubxonaUITests",
    minimum_os_version = "17.0",
    test_host = ":Kutubxona",
    deps = [":UITestsLib"],
)
```

Ishga tushiramiz — va **ikki marta** muammoga duch kelamiz. Bu — haqiqiy, biz uchragan hikoya:

```text
t = 4.14s Waiting 10.0s for "book-1" Cell to exist
t = 5.19s   Checking existence of `"book-1" Cell`
... (10 soniya kutadi, topolmaydi)
/App/UITests/KutubxonaUITests.swift:13: error: ... XCTAssertTrue failed
Test Case '...testRoyxatKitoblarniKorsatadi' failed (14.637 seconds).
...
-- Test timed out at ... UTC --
//App:KutubxonaUITests                                                  TIMEOUT in 300.2s
```

Ikkita alohida muammo yuz berdi:

1. **Test o'zi yiqildi (14.6 soniyada):** `app.cells["book-1"]` hech qachon topilmadi. Sababi — SwiftUI `List` ichidagi view'ga qo'yilgan `.accessibilityIdentifier` o'sha identifikatorni **cell** (`XCUIElement.cells`) sifatida qidirsa mos kelmaydi. Identifikator ichki element'ga tegadi, cell'ning o'ziga emas.
2. **Keyin `xcodebuild` osilib qoldi:** test 14.6 soniyada yiqilgandan so'ng, `xcodebuild` natijani yig'ish/tozalash bosqichida (bir nechta parallel test agentlari simulyatorlarni band qilgan og'ir yuk ostida) qariyb 4.5 daqiqa osilib qoldi. Oxirida Bazel'ning 300 soniyalik timeout'i uni majburan to'xtatdi: `TIMEOUT in 300.2s`.

Log'da yana shunaqa shovqin bor edi: `IDESchemeActionSDKRecord ... invalidDigitCount(94232)` — bu Xcode 27.1'ning build raqamini (24A94232) tahlil qilishdagi zararsiz nosozligi, testga aloqasi yo'q.

**Tuzatish.** 1-muammoni hal qilamiz: `app.cells[...]` o'rniga ishonchliroq so'rovlar ishlatamiz — navigatsiya paneli va ko'rinadigan matn (SwiftUI `List`'da `staticTexts` cell'dan ishonchliroq):

```swift
// App/UITests/KutubxonaUITests.swift — TUZATILGAN (o'tadi)
import XCTest

final class KutubxonaUITests: XCTestCase {
    func testRoyxatKitoblarniKorsatadi() {
        let app = XCUIApplication()
        app.launch()

        // Navigatsiya sarlavhasi "Kutubxona" — eng ishonchli element
        XCTAssertTrue(app.navigationBars["Kutubxona"].waitForExistence(timeout: 20))

        // Kitob sarlavhasi ro'yxatda ko'rinadi
        XCTAssertTrue(app.staticTexts["O'tkan kunlar"].exists)
    }
}
```

```bash
bazel test //App:KutubxonaUITests
```

```text
//App:KutubxonaUITests                                                   PASSED in 26.5s
Executed 1 out of 1 test: 1 test passes.
```

Endi o'tdi — `PASSED in 26.5s`.

> **⚠️ Diqqat:** Bu hikoya UI testlarning haqiqatini ko'rsatadi. Ular (1) **sekin** — bizda `PASSED in 26.5s`, logic testdan ~15 barobar sekin; (2) **mo'rt (flaky)** — noto'g'ri so'rov yoki og'ir yuk ularni yiqitadi; (3) **timeout xavfi bor** — yiqilgan UI test ba'zan toza tugamay, Bazel timeout'igacha osilishi mumkin. Shuning uchun UI testlarni **kam** yozing (faqat eng muhim foydalanuvchi oqimlari uchun) va ularga mos `size`/`timeout` bering (masalan `size = "large"`, 900 s).

Uch xil testni yonma-yon qo'yaylik:

![Uch xil test — logic (1.8s, test_host yo'q), hosted (17.7s, test_host=ilova), UI (15s+, XCUIApplication) — tezligi va qachon ishlatilishi](/lessons/images/bazel/bazel_06_test_kinds.svg)

Bu — 4-darsdagi QA kursida ko'rgan **test piramidasining** Bazel'dagi aksi: ko'p logic test (tez, arzon, pastki qatlam), kamroq hosted test, va juda oz UI test (sekin, mo'rt, yuqori qatlam). Bazel har uchalasining natijasini keshlaydi — lekin yozish arzonrog'i har doim g'olib.

---

## 11. Coverage — kod qamrovi {#coverage}

**Code coverage** (kod qamrovi) — testlaringiz kodning qaysi qatorlarini haqiqatan ishga tushirganini o'lchaydi. 90% qamrov degani: kodning 90% qatorlari kamida bitta test tomonidan bajarilgan. Bu — testlaringizdagi "ko'r nuqtalar"ni topish usuli (hech qachon ishlamaydigan `else` shoxlari, sinalmagan xato yo'llari).

### Qanday ishlaydi

Bazel'da coverage buyrug'i `bazel coverage` (xuddi `bazel test` kabi, lekin instrumentatsiya yoqilgan holda). Mexanizm (Bazel hujjatlaridan):

1. Bazel kodni **instrumentatsiya** qiladi (`--instrumentation_filter` qaysi fayllar qamrovga kirishini belgilaydi; standart holda Bazel target package'ini tanlaydi va buni INFO xabarida chop etadi).
2. Testlar ishga tushadi va bajarilgan qatorlar haqida ma'lumot yig'iladi.
3. Hammasi bitta `lcov` fayliga birlashtiriladi: `$(bazel info output_path)/_coverage/_coverage_report.dat`.

Toza logikani (`KutubxonaKit`) coverage qilishning eng oson yo'li — uni macOS `swift_test` sifatida ham e'lon qilish. Bu bonus: `swift_test` XCTest va Swift Testing'ni **simulyatorsiz**, to'g'ridan-to'g'ri macOS'da ishga tushiradi — tez va coverage uchun qulay:

```starlark
# KutubxonaKit/BUILD.bazel (davomi)
load("@rules_swift//swift:swift.bzl", "swift_test")

swift_test(
    name = "KutubxonaKitSwiftTest",
    srcs = glob(["Tests/**/*.swift"]),
    deps = [":KutubxonaKit"],
)
```

`bazel test //KutubxonaKit:KutubxonaKitSwiftTest` → `PASSED in 1.0s` (simulyatordagi 1.8 s'dan ham tez, chunki simulyator kerak emas). Endi shu target'dan coverage olamiz:

```bash
bazel coverage //KutubxonaKit:KutubxonaKitSwiftTest --combined_report=lcov
```

`lcov` — bu matnli format: har fayl uchun `SF:` (source file), `DA:` (qator raqami, necha marta bajarilgani), `LH:`/`LF:` (qatorlar: hit/found). Uni odam o'qiydigan HTML hisobotga aylantirish uchun `genhtml` ishlatiladi (u `lcov` paketida; `brew install lcov`):

```bash
genhtml --output genhtml "$(bazel info output_path)/_coverage/_coverage_report.dat"
# keyin genhtml/index.html ni brauzerda oching
```

Rule darajasida ikkita foydali atribut bor (rules_apple `ios_unit_test`dan):

- `collect_code_coverage` — shu target uchun qamrov yig'ish (`--collect_code_coverage=yes` bilan).
- `test_coverage_manifest` — qamrov qaysi fayllar bilan cheklanishini belgilaydigan fayl (katta loyihada instrumentatsiyani faqat o'z kodingiz bilan cheklash uchun).

### Ochig'ini aytamiz: bu toolchain'da coverage'ning cheklovi

STYLE talabiga sodiq qolib, haqiqatni aytamiz: biz Bazel 9.3.0 + rules_apple 5.3.0 + rules_swift 4.2.1 + Xcode 27.1 (clang 21) muhitida coverage'ni sinab ko'rdik va **ikki aniq muammoga** duch keldik.

**1. iOS simulyator testida (`ios_unit_test`) coverage qurilmaydi.** `bazel coverage //KutubxonaKit:KutubxonaKitTests` quyidagi linker xatosini beradi:

```text
Undefined symbols for architecture arm64:
  "___llvm_profile_runtime", referenced from:
      ___llvm_profile_runtime_user in libKutubxonaKitTestsLib.a[2](...)
ld: symbol(s) not found for architecture arm64
clang: error: linker command failed with exit code 1
```

Ya'ni instrumentatsiya qilingan kod LLVM profil runtime'iga (`__llvm_profile_runtime`) ishora qiladi, lekin bu runtime (`libclang_rt.profile_iossim.a`, u toolchain'da **mavjud**) test binary'siga ulanmaydi. Bu — rules_swift'ning shu versiyasi + Xcode 27.1 ning o'zaro muammosi (yangi clang 21 yo'llari bilan).

**2. macOS `swift_test`da coverage quriladi va ishlaydi, lekin qator ma'lumoti bo'sh.** Yuqoridagi `swift_test` uchun `bazel coverage` muvaffaqiyatli o'tadi va lcov hisobotini yaratadi — fayllar ro'yxati to'g'ri:

```text
SF:KutubxonaKit/Sources/Book.swift
FNF:0
FNH:0
LH:0
LF:0
SF:KutubxonaKit/Sources/BookLibrary.swift
FNF:0
FNH:0
LH:0
LF:0
```

...

lekin har fayl uchun `LF:0`/`LH:0` (qatorlar topilmadi, bajarilmadi) — ya'ni Swift qamrov ma'lumoti lcov'ga o'girilmadi (instrumentatsiya `swiftc` bilan, lcov konvertatsiyasi esa `llvm-cov` bilan; versiyalar mos kelmasligi odatiy sabab).

> **⚠️ Diqqat:** Demak, `bazel coverage` **buyrug'i ishlaydi** (mexanizm to'g'ri), lekin bu aniq versiyalar to'plamida (2026-oktabr) Swift iOS testlari uchun foydali qator qamrovi bermadi. Bu — Bazel + iOS ekotizimidagi ma'lum "qirrali joy". Amaliyotda jamoalar coverage'ni ko'pincha `rules_xcodeproj` orqali (Xcode'ning o'z coverage mexanizmi bilan) yoki `xcodebuild`'da oladi, yoki Xcode/llvm versiyalarini qat'iy qadab (pinning) muammoni hal qiladi. O'z toolchain'ingizda `bazel coverage`ni sinab ko'ring — versiyalaringizda ishlashi mumkin. (Bu cheklov bizning hisobotimizda ham qayd etilgan.)

> **💡 Pro tip:** Coverage raqami maqsad emas, vosita. 100% qamrov — kod xatosiz degani emas (sifatsiz tekshiruvlar ham qatorni "qoplaydi"). Qamrovni "qaysi muhim yo'llar umuman sinalmagan?" degan savolga javob sifatida ishlating, KPI sifatida emas.

---

## 12. ios_build_test — "shunchaki quriladimi?" {#build-test}

Ba'zan sizda test yozilmagan kutubxona bo'ladi, lekin uning **iOS uchun kompilyatsiya bo'lishini** kafolatlamoqchisiz. Masalan, faqat macOS'da ishlab turgan bo'lsangiz, lekin kod iOS uchun ham qurilishini CI'da tekshirmoqchisiz. Buning uchun `ios_build_test` bor: u kod ishini sinamaydi, faqat ko'rsatilgan target'lar **quriladimi yoki yo'qligini** test qiladi.

```starlark
# KutubxonaKit/BUILD.bazel (davomi)
load("@rules_apple//apple:ios.bzl", "ios_build_test")

ios_build_test(
    name = "KutubxonaKitBuildTest",
    minimum_os_version = "17.0",
    targets = [":KutubxonaKit"],
)
```

`bazel test //KutubxonaKit:KutubxonaKitBuildTest` — agar `KutubxonaKit` iOS uchun qurilsa, "PASSED"; qurilmasa, "FAILED". Simulyator kerak emas, test kodi kerak emas. Bu — eng arzon "ishonchlilik seti": kutubxona kompilyatsiyadan o'tishi kafolatlanadi.

> **💡 Pro tip:** `ios_build_test` ayniqsa faqat bir platformada ishlab, lekin bir nechta platforma uchun qo'llab-quvvatlanadigan kod yozganda foydali. `bazel test //...` butun repoda build_test'larni ham ishga tushiradi — ya'ni bitta buyruq bilan "hamma narsa hamma platformada quriladimi va testlar o'tadimi?" degan savolga javob olasiz.

---

## 13. Katta jamoalarda test strategiyasi {#strategiya}

Hozirgacha o'rgangan narsalar — test keshi, logic/hosted/UI testlar, flaky boshqaruvi — katta jamoalarda nega muhimligini raqamlar bilan ko'raylik. (Diqqat: quyidagi raqamlarning bir qismi vendor e'lon qilgan case study'lardan; biz ularni shunday belgilaymiz.)

> **🏢 Real-world — Spotify (Bazel).** InfoQ'ning 2023-yil oktabrdagi hisobotiga ko'ra, Spotify iOS'da Bazel'ga (remote cache bilan) o'tgach, birlashtirishdan oldingi (p75 pre-merge) kutish vaqti 2022-yil mayidan keyin **"to'rt barobar"** qisqargan; eng og'ir CI konfiguratsiyasi 45+ daqiqadan 10 daqiqadan kamga tushgan. Buning asosi — biz shu darsda ko'rgan narsa: keshlangan natijalar va faqat o'zgarganini qayta ishlaydigan model.

> **🏢 Real-world — Monzo (Tuist, vendor case study).** Tuist 2026-yil 26-mayda e'lon qilgan Monzo case study'siga ko'ra, Monzo'da ~200 modul, 45+ iOS muhandisi, 1.9M qator Swift, **20 ming unit test va 8 ming snapshot test** bor. CI PR'lari 70 daqiqadan oshib ketgan edi; modul keshini har 30 daqiqada `main`'da isitib turish bilan P50 PR vaqti 52 → 15 daqiqaga (−71%) tushgan. 20 mingta testni har safar noldan ishga tushirmaslik — bu Bazel test keshi hal qiladigan aynan o'sha muammo.

> **🏢 Real-world — Trendyol (Tuist, vendor case study).** Trendyol'ning 2024-yil 16-dekabrdagi mijoz hikoyasiga ko'ra, selective (tanlab) unit testlar fikr-mulohaza (feedback) siklini **80% gacha** tezlashtirgan, va UI testlar uchun toza muhit tayyorlash 15+ daqiqadan ~30 soniyagacha qisqargan.

Bu hikoyalarning umumiy g'oyasi bitta: **o'zgarmagan testni qayta ishga tushirma.** Tuist buni "selective testing" deb ataydi (target hash o'zgarganini test qiladi; target darajasida). Bazel buni ikki mexanizm bilan qiladi:

1. **Test natijasi keshi** (shu darsda ko'rdik): o'zgarmagan testlar `(cached) PASSED`.
2. **Faqat ta'sirlangan target'larni test qilish**: `git diff` → o'zgargan fayllar → ularga bog'liq test target'larini `bazel query 'rdeps(//..., set(...))'` bilan topish va faqat ularni ishga tushirish. Buni 11- va 16-darslarda to'liq quramiz.

> **🧠 Esda tuting:** Bazel test keshi "bepul" selective testing beradi — `bazel test //...` har doim o'zgarmaganlarini keshdan oladi. Lekin eng katta tejash CI'da **remote cache** bilan keladi: bitta muhandis (yoki `main` build'i) test natijasini remote cache'ga yozadi, qolganlar esa uni yuklab oladi va umuman qayta ishlamaydi. Bu — 10- va 11-darslarning mavzusi.

> **⚠️ Diqqat:** Yuqoridagi Monzo va Trendyol raqamlari Tuist blogida e'lon qilingan mijoz hikoyalaridan (vendor manbasi), mustaqil o'lchov emas. Spotify raqami InfoQ hisoboti orqali. Bazel vs Tuist vs Xcode uchun neytral, mustaqil benchmark mavjud emas — har bir raqam o'sha kompaniyaning avvalgi holatiga nisbatan. Bu haqda 20-darsda batafsil to'xtalamiz.

---

## Amaliy mashqlar {#mashqlar}

1. **Yangi metod + test.** `BookLibrary`'ga `averagePages: Int` (o'rtacha sahifa soni, kitoblar bo'sh bo'lsa 0) qo'shing. Unga ham XCTest, ham Swift Testing testi yozing. `bazel test`'da ikkala framework summary'si ham chiqishini tekshiring.

2. **Filtrlash.** `--test_filter` bilan faqat `testSortedByYear` metodini ishga tushiring. Keyin regular expression bilan `testSearch*` va `testSort*` ni birga ishga tushirishga harakat qiling.

3. **size to'g'rilash.** `KutubxonaKitTests`ga `size = "small"` qo'shing va `bazel test //KutubxonaKit:KutubxonaKitTests --test_verbose_timeout_warnings` bilan "size is too big" ogohlantirishi yo'qolganini tasdiqlang.

4. ⭐ **Flaky testni ushlash.** Bir testda `#expect(Bool.random())` yoki shunga o'xshash beqaror shart yozing. `--runs_per_test=20` bilan ishga tushiring va ba'zi urinishlar yiqilishini ko'ring. Keyin target'ga `flaky = True` qo'shib, xatti-harakat qanday o'zgarishini kuzating. (Tajribadan so'ng beqaror testni o'chiring — flaky testlar repoda qolmasin.)

5. ⭐ **Tasodifiy tartib.** `KutubxonaKitTests` runner'ini `ios_xctestrun_random_runner`ga o'zgartiring va testlar tasodifiy tartibda o'tishini tekshiring. Testlaringiz bir-biriga bog'liq emasligiga ishonch hosil qiling.

6. ⭐ **UI testni kengaytirish.** `KutubxonaUITests`ga ikkinchi tekshiruv qo'shing: ro'yxatda uchala kitob ham ko'rinishini (`app.staticTexts` bilan) tasdiqlang. UI test `size`ini `"large"` qiling va nega buni tanlaganingizni izohlang.

---

## O'zingizni tekshiring {#checklist}

- [ ] Test kodini nega alohida `swift_library` (`testonly = True`) ga ajratish kerakligini tushuntira olaman.
- [ ] Bitta `ios_unit_test`da XCTest va Swift Testing'ni birga ishlata olaman.
- [ ] `bazel test`, `--test_output=errors|all`, `--test_filter` ni kundalik ishda qo'llayman.
- [ ] `(cached) PASSED` qachon chiqishini va nima uni buzishini aytib bera olaman.
- [ ] `size`/`timeout` ning ma'nosini va standart qiymatlarini bilaman (`small` → 60 s).
- [ ] `--runs_per_test`, `flaky = True`, `shard_count` qachon kerakligini bilaman.
- [ ] Logic test, hosted test (`test_host`) va UI test (`ios_ui_test`) farqini va tezlik farqini tushunaman.
- [ ] Xcode 27'da simulyatorni `.bazelrc` orqali to'g'ri tanlay olaman.

---

## Xulosa {#xulosa}

- Bazel'da test strukturasi doim uch qismli: asosiy `swift_library` → `testonly` test `swift_library` → `ios_unit_test`.
- XCTest ham, Swift Testing ham bitta target'da ishlaydi — rules_swift ikkalasini tug'ma qo'llaydi.
- Bazel'ning asosiy super-kuchi — **test natijasi keshi**: o'zgarmagan test `(cached) PASSED` beradi va simulyatorda umuman ishlamaydi (bizda 1.8 s o'rniga 0.19 s).
- `size`/`timeout`/`flaky`/`shard_count` — testlar sifatini va tezligini boshqarish vositalari; eng kichik mos `size`ni tanlang.
- Uch xil test uch xil tezlik: logic (~1.8 s), hosted (~17.7 s), UI (~26.5 s) — shuning uchun ko'p logic, oz UI test yozing.
- UI testlar mo'rt va timeout'ga moyil — biz buni haqiqiy hikoyada ko'rdik (noto'g'ri so'rov + xcodebuild osilishi → `TIMEOUT in 300.2s`).
- Simulyatorni `.bazelrc`'da build sozlamasi orqali tanlang — Xcode 27'dagi "iPhone 15" tuzog'idan shunday qutulasiz.
- Coverage toza logika uchun `swift_test` orqali oson olinadi; iOS simulyator testlari uchun bu toolchain'da hozircha cheklov bor (quyida ko'rdik).

> **Keyingi dars:** [Bazel 7: Tashqi dependency'lar — Bzlmod, BCR, SPM paketlari va xcframework'lar](/ios-lessons/bazel-07-dependency) — endi loyihamizga tashqi kutubxonalarni (SnapKit, swift-collections kabi) Bazel orqali qanday qo'shishni o'rganamiz.

---

## Manbalar {#manbalar}

1. [rules_apple — rules-ios.md (ios_unit_test, ios_ui_test, ios_build_test, ios_xctestrun_runner)](https://github.com/bazelbuild/rules_apple/blob/main/doc/rules-ios.md)
2. [rules_apple — common_info.md (test runfiles, Xcode versiya tanlash)](https://github.com/bazelbuild/rules_apple/blob/main/doc/common_info.md)
3. [rules_apple releases (5.0.0 — standart test runner xctestrun'ga o'tdi)](https://github.com/bazelbuild/rules_apple/releases)
4. [rules_swift — swift_test, XCTest discovery, test filtering](https://github.com/bazelbuild/rules_swift/blob/main/doc/rules.md)
5. [Bazel — Code coverage (lcov, genhtml, instrumentation)](https://bazel.build/configure/coverage)
6. [Bazel — Test encyclopedia (size, timeout, sharding)](https://bazel.build/reference/test-encyclopedia)
7. [Swift Testing (Apple hujjatlari)](https://developer.apple.com/documentation/testing)
8. [Spotify iOS Bazel'ga o'tishi (InfoQ, 2023-oktabr)](https://www.infoq.com/news/2023/10/spotify-bazel-ios-transition/)
9. [Monzo case study (Tuist, 2026-05-26 — vendor)](https://github.com/tuist/tuist/blob/main/server/priv/marketing/case_studies/monzo.md)
10. [Trendyol case study (Tuist, 2024-12-16 — vendor)](https://tuist.dev/blog/2024/12/16/trendyol)
