ОНОЛ 03

Програм Хангамжийн Тестчилэл

ЛЕКЦ 03: ПРОГРАМ ХАНГАМЖИЙН ТЕСТЧИЛЭЛ (Software Testing)

Хичээлийн зорилго: Програм хангамжийн тестчилэлийн суурь ойлголт, аргачлалууд, түвшнүүд, JUnit фреймворк ашиглан тест бичих чадвар эзэмшүүлэх.

Хамрах хүрээ: Тестчилэлийн түвшнүүд, Unit Testing, Integration Testing, TDD, JUnit 5, Code Coverage, тест стратегиуд.



ХЭСЭГ 1: ОНОЛЫН СУУРЬ (Theory & Foundations)

1.1 Тестчилэл гэж юу вэ?

Програм хангамжийн тестчилэл (Software Testing) гэдэг нь програм зөв ажиллаж байгаа эсэхийг шалгах, алдааг (bug) олох, чанарыг баталгаажуулах процесс юм.

🔑 IEEE тодорхойлолт: "Тестчилэл нь програмыг хүлээгдэж буй үр дүнтэй нь харьцуулж, зөрүүг олох процесс."

Тестчилэл яагаад чухал вэ?

Барилга барьж дууссаны дараа газар хөдлөлтийн шалгалт хийдэг шиг, програм хангамж мөн хэрэглэгчид хүрэхээс өмнө сайтар шалгагдах ёстой.

Бодит жишээ — Алдааны үнэ:

Алдааг олсон үе шатЗасах зардал (харьцангуй)
Шаардлага тодорхойлолт
Дизайн
Кодчилол10×
Тестчилэл20×
Хэрэглэгчид хүрсний дараа100×

💡 Зүйрлэл: Хувцас оёхдоо хэмжилт алдвал эхэндээ засахад хялбар. Бэлэн хувцас болсны дараа засвар хийхэд их цаг, мөнгө шаардагдана. Код мөн адил.

Тестчилэлийн гол зорилгууд:

  1. Алдаа олох — Код дахь буруу логик, бичлэгийн алдааг олж засах
  2. Чанар баталгаажуулах — Шаардлагын дагуу зөв ажиллаж байгааг батлах
  3. Итгэлцэл бий болгох — Програмд итгэж болно гэсэн баталгаа
  4. Регрессийн хамгаалалт — Шинэ код нэмэхэд хуучин функц эвдрэхгүй байх
  5. Баримтжуулалт — Тест нь кодын зан төлөвийн "амьд" баримт бичиг

1.2 Тестчилэлийн суурь зарчмууд (7 зарчим — ISTQB)

ISTQB (International Software Testing Qualifications Board) нь 7 суурь зарчмыг тодорхойлсон:

Зарчим 1: Тестчилэл нь алдаа байгааг харуулдаг, алдаагүйг биш

Зүйрлэл: Шөнийн харанхуйд гэрлээр гэрийг шалгахад бөөс олдсон бол — бөөс БАЙНА гэсэн үг. Олдоогүй бол — БАЙХГҮЙ гэсэн үг БИШ, зүгээр олдоогүй гэсэн үг.

Тестчилэл нь алдааг олж чадна, гэхдээ алдаа огт байхгүй гэдгийг батлах чадваргүй. 100 тест дамжсан ч 101 дэх тохиолдолд алдаа гарч болно.

Зарчим 2: Бүрэн тестчилэл боломжгүй (Exhaustive testing is impossible)

Зүйрлэл: Хэрэв нэг функц 3 параметр авдаг, параметр бүр 100 утга авч болдог бол: 100 × 100 × 100 = 1,000,000 тохиолдол. Бүгдийг тестлэх боломжгүй!

Бүх оролт, бүх нөхцөлийг тестлэх нь бараг боломжгүй. Тиймээс эрсдэлд суурилсан тест стратеги ашиглана.

Зарчим 3: Эрт тестлэх (Early testing)

Зүйрлэл: Хорт хавдрыг эрт оношлох тусмаа эдгэрэх магадлал өндөр. Алдааг эрт олох тусмаа засах зардал бага.

Тестчилэлийг зөвхөн кодчилол дууссаны дараа биш, шаардлага, дизайны шатнаас эхлэх ёстой.

Зарчим 4: Алдааны бөөгнөрөл (Defect clustering)

Зүйрлэл: "Парето зарчим" — Алдааны 80% нь кодын 20%-д байдаг.

Програмын нарийн төвөгтэй, олон удаа өөрчлөгдсөн хэсгүүдэд алдаа бөөгнөрдөг. Тэр хэсгүүдэд анхаарал төвлөрүүлэх ёстой.

Зарчим 5: Хортон шавжны эм (Pesticide paradox)

Зүйрлэл: Нэг л хортон шавжны эмийг давтан хэрэглэвэл шавж дасдаг. Ижил тестийг давтвал шинэ алдаа олдохгүй.

Тестийг байнга шинэчлэх, шинэ тохиолдлууд нэмэх шаардлагатай. Хуучин тестээр шинэ алдаа олдохгүй.

Зарчим 6: Тестчилэл нь контекстаас хамаарна

Зүйрлэл: Нисэх онгоцны програм ба мобайл тоглоомын тест адилхан биш. Нисэх онгоцны программ алдаа гаргавал хүмүүсийн амь насанд аюултай, тоглоом алдаа гаргавал зүгээр дахин эхэлнэ.

Тестийн стратеги нь програмын төрөл, эрсдэл, зорилго-оос хамаарна.

Зарчим 7: Алдаагүй гэсэн төөрөгдөл (Absence-of-errors fallacy)

Зүйрлэл: 0 алдаатай програм ч хэрэглэгчийн шаардлагад нийцэхгүй байж болно.

Алдаагүй програм ≠ Зөв програм. Програм хэрэглэгчийн хэрэгцээг хангаж чадаж байгаа эсэх нь чухал.


1.3 Тестчилэлийн түвшнүүд (Testing Levels)

Тестчилэл нь пирамид бүтэцтэй — доороос дээш:

         ▲
        / \         ← E2E Testing (Системийн бүрэн тест)
       /   \           Удаан, цөөн
      /     \
     / Integra-\    ← Integration Testing (Нэгтгэлийн тест)
    /   tion    \      Дунд зэрэг
   /             \
  /  Unit Testing \  ← Unit Testing (Нэгж тест)
 /                 \    Хурдан, маш олон
/___________________\

1.3.1 Unit Testing (Нэгж тест)

Тодорхойлолт: Кодын хамгийн ЖИЖИГ нэгжийг (метод, функц) тусад нь шалгах.

Зүйрлэл: Машинд суурилуулахаас өмнө мотор, тоормос, гэрэл тус бүрийг ТУСАД НЬ шалгах. Мотор зөв ажиллаж байна уу? Тоормос зөв зогсоож байна уу?

Онцлог:

  • Хамгийн хурдан — миллисекунд хугацаанд ажиллана
  • Хамгийн олон — тестийн 70-80% нь unit тест
  • Хамгийн хямд — олж засахад хялбар
  • Хөгжүүлэгч бичдэг — код бичсэн хүн бичнэ
// Unit тестийн жишээ — Calculator классын нэмэх методыг тестлэх
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;

class CalculatorTest {

    @Test
    void testAdd() {
        Calculator calc = new Calculator();
        assertEquals(5, calc.add(2, 3));      // 2 + 3 = 5 байх ёстой
    }

    @Test
    void testAddNegative() {
        Calculator calc = new Calculator();
        assertEquals(-1, calc.add(2, -3));    // 2 + (-3) = -1 байх ёстой
    }

    @Test
    void testAddZero() {
        Calculator calc = new Calculator();
        assertEquals(0, calc.add(0, 0));      // 0 + 0 = 0 байх ёстой
    }
}

1.3.2 Integration Testing (Нэгтгэлийн тест)

Тодорхойлолт: Олон модуль, компонентыг НЭГТГЭЖ шалгах. Модулиуд хоорондоо зөв харилцаж байгаа эсэхийг баталгаажуулах.

Зүйрлэл: Мотор, тоормос, хурдны хайрцаг тус бүрдээ зөв ажилладаг ч хамтдаа зөв ажиллаж байгаа эсэхийг шалгах. Мотор хурдны хайрцагтай зөв холбогдож байна уу?

Нэгтгэлийн тестийн аргууд:

АргаТайлбарЗүйрлэл
Big BangБүх модулийг нэг дор нэгтгэж тестлэхБүх эд ангийг нэг дор угсарч шалгах
Top-DownДээрээс доош нэгтгэж тестлэхДээврээс эхэлж барилга барих
Bottom-UpДоороос дээш нэгтгэж тестлэхСуурьнаас эхэлж барилга барих
SandwichTop-Down + Bottom-Up хослуулахДундаас хоёр зүгт зэрэг барих
// Integration тестийн жишээ — UserService ба Database хамтран ажиллаж байгааг шалгах
class UserServiceIntegrationTest {

    @Test
    void testRegisterAndFindUser() {
        Database db = new Database("test.db");
        UserService service = new UserService(db);

        // Хэрэглэгч бүртгэх
        service.register("Бат", "bat@email.com");

        // Бүртгэсэн хэрэглэгчийг олох
        User found = service.findByEmail("bat@email.com");

        assertNotNull(found);                         // Олдсон байх ёстой
        assertEquals("Бат", found.getName());         // Нэр тохирч байх ёстой
    }
}

1.3.3 System Testing (Системийн тест)

Тодорхойлолт: Бүхэл системийг шаардлагын дагуу ажиллаж байгаа эсэхийг шалгах.

Зүйрлэл: Бүрэн угсарсан машиныг замд гаргаж жолоодож шалгах — бүх зүйл хамтдаа зөв ажиллаж байна уу?

1.3.4 Acceptance Testing (Хүлээн авах тест)

Тодорхойлолт: Хэрэглэгч / захиалагч системийг хүлээн авахын өмнө хийх тест.

Зүйрлэл: Машин худалдан авагч жолоодож туршаад "тийм, энэ машин надад тохирч байна" гэж хэлэх.

Төрлүүд:

  • Alpha тест — Хөгжүүлэгч байгууллагын дотор
  • Beta тест — Бодит хэрэглэгчдэд тарааж шалгуулах
  • UAT (User Acceptance Testing) — Захиалагч шалгах

1.4 Тестийн аргачлалууд (Testing Techniques)

1.4.1 Хар хайрцагны тест (Black-Box Testing)

Тодорхойлолт: Кодын дотоод бүтцийг МЭДЭХГҮЙГЭЭР, зөвхөн оролт → гаралтыг шалгах.

Зүйрлэл: Вендинг машин. Мөнгө хийж, товч дарна → Бүтээгдэхүүн гарна. Дотор нь яаж ажиллаж байгааг мэдэх шаардлагагүй.

Техникүүд:

а) Эквивалент ангилал (Equivalence Partitioning)

Оролтыг "ижил зан төлөвтэй" бүлгүүдэд хуваана. Бүлэг бүрээс нэг утга тестлэхэд хангалттай.

Жишээ: Насны баталгаажуулалт (18-65 зөвшөөрөгддөг)
├── Бүлэг 1: < 18  (буруу) → Тест: 10
├── Бүлэг 2: 18-65 (зөв)  → Тест: 30
└── Бүлэг 3: > 65  (буруу) → Тест: 70

б) Хил заагийн шинжилгээ (Boundary Value Analysis)

Хилийн утгууд дээр алдаа хамгийн их гардаг → Хилийн утгуудыг онцгойлон тестлэх.

Жишээ: Насны баталгаажуулалт (18-65)
Тестлэх утгууд: 17, 18, 19, 64, 65, 66
                 ↑   ↑       ↑   ↑
              Хилийн утгууд дээр алдаа их гардаг!

в) Шийдвэрийн хүснэгт (Decision Table)

Олон нөхцөлийн хослолуудыг хүснэгтээр тестлэх:

| Нөхцөл         | Тест 1 | Тест 2 | Тест 3 | Тест 4 |
|-----------------|--------|--------|--------|--------|
| Нас >= 18       | Тийм   | Тийм   | Үгүй   | Үгүй   |
| Жолооны үнэмлэх | Тийм   | Үгүй   | Тийм   | Үгүй   |
| Машин жолоодох   | ✅ Зөв  | ❌ Буруу | ❌ Буруу | ❌ Буруу |

1.4.2 Цагаан хайрцагны тест (White-Box Testing)

Тодорхойлолт: Кодын дотоод бүтцийг МЭДЭЖ, бүх замналуудыг шалгах.

Зүйрлэл: Машины механик моторыг задалж, дотоод эд анги бүрийг шалгах.

Техникүүд:

а) Мэдэгдлийн хамралт (Statement Coverage)

Кодын бүх мэдэгдэл (statement) дор хаяж 1 удаа ажиллаж байгааг шалгах.

// Шалгах код
public String getGrade(int score) {
    if (score >= 90) {           // Мэдэгдэл 1
        return "A";              // Мэдэгдэл 2
    } else if (score >= 80) {    // Мэдэгдэл 3
        return "B";              // Мэдэгдэл 4
    } else {
        return "F";              // Мэдэгдэл 5
    }
}

// 100% statement coverage-д:
// Тест 1: score = 95 → "A"  (Мэдэгдэл 1, 2)
// Тест 2: score = 85 → "B"  (Мэдэгдэл 3, 4)
// Тест 3: score = 50 → "F"  (Мэдэгдэл 5)

б) Салаалтын хамралт (Branch Coverage)

Бүх if/else салаалтын TRUE ба FALSE замыг тестлэх.

в) Замналын хамралт (Path Coverage)

Кодын бүх боломжит замналуудыг тестлэх — хамгийн иж бүрэн гэхдээ хамгийн их хүчин чармайлт шаарддаг.

1.4.3 Саарал хайрцагны тест (Grey-Box Testing)

Хар + Цагаан хайрцагны хослол. Дотоод бүтцийн ЗАРИМ мэдлэгтэй байж тестлэх.

Зүйрлэл: Машины механик мотороо мэддэг (цагаан) гэхдээ дотор бүрэн задлахгүйгээр, гаднаас оношлох (хар). Хоёрыг хослуулах.


1.5 Test-Driven Development (TDD)

TDD нь "Тестээ ЭХЛЭЭД бич, дараа нь кодоо бич" гэсэн хөгжүүлэлтийн арга.

TDD-ийн 3 алхам (Red-Green-Refactor):

    ┌─────────┐
    │  🔴 RED  │  ← 1. Шинэ тест бич (тест FAIL болно)
    └────┬────┘
         │
    ┌────▼────┐
    │ 🟢 GREEN│  ← 2. Тестийг дамжуулах ХАМГИЙН БАГА кодыг бич
    └────┬────┘
         │
    ┌────▼─────┐
    │🔵REFACTOR│  ← 3. Кодыг цэвэрлэ, сайжруул (тест дахин дамждаг)
    └────┬─────┘
         │
         └─────── 🔄 Давтана

TDD-ийн бодит жишээ: FizzBuzz

Даалгавар: 1-100 хүртэлх тоонуудыг хэвлэх. 3-д хуваагддаг бол "Fizz", 5-д хуваагддаг бол "Buzz", хоёуланд нь хуваагддаг бол "FizzBuzz".

Алхам 1 — 🔴 RED: Тест бичих

@Test
void testFizzBuzzOf1() {
    assertEquals("1", FizzBuzz.convert(1));   // 1 → "1"
}

Алхам 2 — 🟢 GREEN: Хамгийн бага кодыг бичих

class FizzBuzz {
    public static String convert(int number) {
        return String.valueOf(number);        // Зүгээр тоог String болгох
    }
}

Алхам 3 — 🔴 RED: Шинэ тест нэмэх

@Test
void testFizzBuzzOf3() {
    assertEquals("Fizz", FizzBuzz.convert(3));  // 3 → "Fizz"
}

Алхам 4 — 🟢 GREEN: Кодыг өргөтгөх

class FizzBuzz {
    public static String convert(int number) {
        if (number % 3 == 0) return "Fizz";
        return String.valueOf(number);
    }
}

Алхам 5 — 🔴 RED → 🟢 GREEN: 5-д хуваагддаг

@Test
void testFizzBuzzOf5() {
    assertEquals("Buzz", FizzBuzz.convert(5));  // 5 → "Buzz"
}

// Код:
class FizzBuzz {
    public static String convert(int number) {
        if (number % 3 == 0 && number % 5 == 0) return "FizzBuzz";
        if (number % 3 == 0) return "Fizz";
        if (number % 5 == 0) return "Buzz";
        return String.valueOf(number);
    }
}

TDD-ийн давуу ба сул талууд:

Давуу талСул тал
Алдааг эрт олдогЭхэндээ удаан
Кодын чанар сайжирдагСурах муруй өндөр
Дахин засварлахад (refactor) итгэлтэйБүх нөхцөлд тохирохгүй (UI тест)
Баримтжуулалт болдогТест maintenance шаардагддаг
Дизайныг сайжруулдагХэт нарийн тестчилэлд хүргэж болно

1.6 JUnit 5 фреймворк

JUnit нь Java-ийн хамгийн алдартай unit тестийн фреймворк. JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage.

JUnit 5-ийн гол аннотациуд:

АннотациЗорилгоЗүйрлэл
@TestМетод нь тест гэдгийг заах"Энэ бол шалгалт" гэсэн шошго
@BeforeEachТест бүрийн өмнө ажиллахШалгалтын өмнө ширээ цэвэрлэх
@AfterEachТест бүрийн дараа ажиллахШалгалтын дараа ширээ цэвэрлэх
@BeforeAllБүх тестийн өмнө 1 удаа ажиллахШалгалтын өрөө бэлтгэх
@AfterAllБүх тестийн дараа 1 удаа ажиллахШалгалтын өрөө хаах
@DisplayNameТестийн нэрийг тодорхойлохШалгалтын асуултын гарчиг
@DisabledТестийг түр хаах"Энэ асуултыг алгас"
@ParameterizedTestОлон утгаар тестлэхОлон оюутнаар ижил шалгалт авах
@RepeatedTestТестийг олон удаа давтахШалгалтыг давтан авах

JUnit 5-ийн гол assert методууд:

// Тэнцүү эсэх
assertEquals(expected, actual);              // expected == actual
assertNotEquals(unexpected, actual);         // unexpected != actual

// Null эсэх
assertNull(object);                          // object == null
assertNotNull(object);                       // object != null

// Boolean
assertTrue(condition);                       // condition == true
assertFalse(condition);                      // condition == false

// Exception шалгах
assertThrows(Exception.class, () -> {
    // Exception үүсгэх код
});

// Хугацааны хязгаар
assertTimeout(Duration.ofSeconds(2), () -> {
    // 2 секундэд дуусах ёстой код
});

// Массив тэнцүү эсэх
assertArrayEquals(expectedArray, actualArray);

JUnit 5 бүрэн жишээ: BankAccount тест

import org.junit.jupiter.api.*;
import static org.junit.jupiter.api.Assertions.*;

// ===== Тестлэх класс =====
class BankAccount {
    private String owner;
    private double balance;

    public BankAccount(String owner, double initialBalance) {
        if (initialBalance < 0) {
            throw new IllegalArgumentException("Анхны үлдэгдэл сөрөг байж болохгүй");
        }
        this.owner = owner;
        this.balance = initialBalance;
    }

    public void deposit(double amount) {
        if (amount <= 0) {
            throw new IllegalArgumentException("Нэмэх дүн 0-ээс их байх ёстой");
        }
        balance += amount;
    }

    public void withdraw(double amount) {
        if (amount <= 0) {
            throw new IllegalArgumentException("Авах дүн 0-ээс их байх ёстой");
        }
        if (amount > balance) {
            throw new IllegalStateException("Үлдэгдэл хүрэлцэхгүй байна");
        }
        balance -= amount;
    }

    public double getBalance() { return balance; }
    public String getOwner() { return owner; }
}

// ===== Тест класс =====
class BankAccountTest {

    private BankAccount account;

    @BeforeEach
    void setUp() {
        // Тест бүрийн өмнө шинэ данс үүсгэнэ
        account = new BankAccount("Бат", 10000);
    }

    @Test
    @DisplayName("Данс үүсгэхэд үлдэгдэл зөв байх")
    void testInitialBalance() {
        assertEquals(10000, account.getBalance());
        assertEquals("Бат", account.getOwner());
    }

    @Test
    @DisplayName("Мөнгө нэмэхэд үлдэгдэл нэмэгдэх")
    void testDeposit() {
        account.deposit(5000);
        assertEquals(15000, account.getBalance());    // 10000 + 5000
    }

    @Test
    @DisplayName("Мөнгө авахад үлдэгдэл хасагдах")
    void testWithdraw() {
        account.withdraw(3000);
        assertEquals(7000, account.getBalance());     // 10000 - 3000
    }

    @Test
    @DisplayName("Үлдэгдэлээс их мөнгө авахад алдаа гарах")
    void testWithdrawInsufficientFunds() {
        assertThrows(IllegalStateException.class, () -> {
            account.withdraw(50000);                  // 10000-аас их → Алдаа!
        });
    }

    @Test
    @DisplayName("Сөрөг дүн нэмэхэд алдаа гарах")
    void testDepositNegativeAmount() {
        assertThrows(IllegalArgumentException.class, () -> {
            account.deposit(-1000);                   // Сөрөг дүн → Алдаа!
        });
    }

    @Test
    @DisplayName("Сөрөг анхны үлдэгдлээр данс үүсгэхэд алдаа гарах")
    void testNegativeInitialBalance() {
        assertThrows(IllegalArgumentException.class, () -> {
            new BankAccount("Болд", -5000);           // Сөрөг → Алдаа!
        });
    }

    @Test
    @DisplayName("Олон удаа мөнгө нэмж авахад зөв тооцоолох")
    void testMultipleTransactions() {
        account.deposit(5000);                        // 10000 + 5000 = 15000
        account.withdraw(3000);                       // 15000 - 3000 = 12000
        account.deposit(1000);                        // 12000 + 1000 = 13000
        assertEquals(13000, account.getBalance());
    }
}

1.7 Code Coverage (Кодын хамралт)

Code Coverage гэдэг нь тестээр кодын хэдэн хувь хамрагдсан бэ гэдгийг хэмждэг.

Зүйрлэл: Гэрийн шалгалтаар бүх өрөө шалгагдсан уу? 10 өрөөнөөс 8-ыг шалгасан бол 80% хамралт.

Хамралтын төрлүүд:

ТөрөлТайлбарЖишээ
Statement CoverageКодын мөр бүр ажиллаж байна уу20 мөрнөөс 18 ажилласан → 90%
Branch Coverageif/else бүр хоёр чиглэлд тестлэгдсэн үү10 салаанаас 8 тестлэгдсэн → 80%
Method CoverageМетод бүр дуудагдсан уу5 методоос 5 дуудагдсан → 100%
Line CoverageМөр бүр ажиллаж байна ууStatement Coverage-тэй төстэй

Хамралтын зөвлөмж:

Хамралтын түвшинҮнэлгээ
< 50%❌ Маш муу — олон алдаа нуугдаж байна
50-70%⚠️ Дунд зэрэг — сайжруулах хэрэгтэй
70-85%✅ Сайн — ихэнх төслийн стандарт
85-95%✅ Маш сайн — чухал системүүдэд зөвлөгддөг
100%⚠️ Хэтэрхий — 100% хамралт ≠ 100% алдаагүй

⚠️ Анхааруулга: 100% coverage нь алдаагүй гэсэн үг биш! Тест нь зөв зүйлийг шалгаж байгаа эсэх (test quality) нь coverage-ээс чухал.


1.8 Тестийн сайн практикууд (Best Practices)

AAA загвар (Arrange-Act-Assert):

@Test
void testDiscount() {
    // ARRANGE — Бэлтгэх
    Product product = new Product("Ном", 20000);
    DiscountService service = new DiscountService();

    // ACT — Үйлдэл хийх
    double discountedPrice = service.applyDiscount(product, 10);

    // ASSERT — Шалгах
    assertEquals(18000, discountedPrice);     // 20000 - 10% = 18000
}

FIRST зарчмууд:

ЗарчимАнглиМонгол тайлбар
FFastТест ХУРДАН ажиллах ёстой
IIndependentТестүүд бие биенээсээ ХАМААРАЛГҮЙ
RRepeatableХэзээ ч ажиллуулсан ИЖИЛ үр дүн
SSelf-validatingТест ӨӨрӨӨ ЗӨРҮҮ/ДАМЖСАН гэж хариулах
TTimelyТестийг ЦАГТ НЬ бичих (кодтой зэрэг)

Нэг тест — нэг зүйл шалгах:

// ❌ БУРУУ — олон зүйл нэг тестэд
@Test
void testEverything() {
    assertEquals(5, calc.add(2, 3));
    assertEquals(6, calc.multiply(2, 3));     // Хоёр өөр функц нэг тестэд
    assertNotNull(calc.toString());
}

// ✅ ЗӨӨВ — тус тусдаа
@Test
void testAdd() {
    assertEquals(5, calc.add(2, 3));
}

@Test
void testMultiply() {
    assertEquals(6, calc.multiply(2, 3));
}

Тестийн нэрлэх дүрэм:

// Загвар: methodName_condition_expectedResult
@Test void add_twoPositiveNumbers_returnsSum() { ... }
@Test void withdraw_insufficientBalance_throwsException() { ... }
@Test void login_invalidPassword_returnsFalse() { ... }

1.9 Mock ба Stub

Тестлэх объект бусад объектоос хамааралтай бол Mock ба Stub ашиглана.

Зүйрлэл: Жүжигчин бодит дайсантай биш, хиймэл манекентай дасгал хийдэг шиг — тест бодит мэдээллийн сантай биш, хиймэл объекттой ажиллана.

Stub vs Mock:

StubMock
ЗорилгоБэлэн хариу буцаахЗан төлөвийг шалгах
ЗүйрлэлАвтомат хариулагч машинХэн юу хэлснийг бичиж авдаг нарийн бичгийн дарга
Шалгах зүйлГаралтын утгаМетод дуудагдсан эсэх, хэдэн удаа дуудагдсан
// Stub жишээ — Тогтмол хариу буцаадаг
class StubEmailService implements EmailService {
    @Override
    public boolean send(String to, String message) {
        return true;   // Үргэлж амжилттай гэж буцаана
    }
}

// Mock жишээ (Mockito ашиглан)
@Test
void testUserRegistration() {
    EmailService mockEmail = mock(EmailService.class);     // Mock үүсгэх
    UserService service = new UserService(mockEmail);

    service.register("Бат", "bat@email.com");

    // Баталгаажуулах: send метод 1 удаа дуудагдсан уу?
    verify(mockEmail, times(1)).send(eq("bat@email.com"), anyString());
}

1.10 Тестийн төрлүүд (Functional vs Non-Functional)

Функциональ тест (Functional Testing):

Систем юу хийдэг-ийг шалгана.

ТөрөлТайлбар
Unit TestНэгж функцийн шалгалт
Integration TestМодулиудын хамтын ажиллагаа
System TestБүхэл системийн шалгалт
Acceptance TestХэрэглэгчийн шаардлага хангаж буй эсэх
Regression TestШинэ код хуучин функцийг эвдсэн эсэх
Smoke TestСистемийн суурь функцүүд ажиллаж байна уу (хурдан шалгалт)

Функциональ бус тест (Non-Functional Testing):

Систем яаж хийдэг-ийг шалгана.

ТөрөлТайлбарЗүйрлэл
Performance TestХурд, хариу өгөх хугацааМашин хэр хурдан вэ?
Load TestАчаалал дор ажиллагаа1000 хүн нэг зэрэг нэвтэрвэл?
Stress TestХязгаарын ачаалалМашин хамгийн ихдээ хэр хурдалж чадах вэ?
Security TestАюулгүй байдалХаалга, цонх түгжигдсэн үү?
Usability TestХэрэглэхэд хялбар эсэхМашин жолоодоход хялбар уу?
Compatibility TestӨөр орчинд ажиллах эсэхМашин шороон замд ч зүгээр юу?


ХЭСЭГ 2: ТҮЛХҮҮР ҮГ БА МЭРГЭЖЛИЙН НЭР ТОМЬЁО (Keywords & Glossary)

#Англи нэр томьёоМонгол утгаДэлгэрэнгүй тайлбар
1Software TestingПрограм хангамжийн тестчилэлПрограм зөв ажиллаж байгаа эсэхийг шалгах, алдааг олох, чанарыг баталгаажуулах процесс.
2Unit TestingНэгж тестКодын хамгийн жижиг нэгжийг (метод, функц) тусад нь шалгах. Хамгийн хурдан, хамгийн олон тест.
3Integration TestingНэгтгэлийн тестОлон модуль нэгдэж зөв ажиллаж байгаа эсэхийг шалгах.
4System TestingСистемийн тестБүхэл системийг шаардлагын дагуу шалгах.
5Acceptance TestingХүлээн авах тестХэрэглэгч / захиалагч системийг хүлээн авахын өмнө хийх тест.
6Regression TestingРегрессийн тестШинэ код хуучин функцийг эвдсэн эсэхийг шалгах.
7Smoke TestingУтааны тестСистемийн суурь функцүүд ажиллаж байна уу гэдгийг хурдан шалгах.
8Black-Box TestingХар хайрцагны тестКодын дотоод бүтцийг мэдэхгүйгээр оролт → гаралтыг шалгах.
9White-Box TestingЦагаан хайрцагны тестКодын дотоод бүтцийг мэдэж бүх замналуудыг шалгах.
10Grey-Box TestingСаарал хайрцагны тестХар + Цагаан хайрцагны хослол. Дотоод бүтцийн зарим мэдлэгтэй.
11TDD (Test-Driven Development)Тестэнд суурилсан хөгжүүлэлтТестээ эхлээд бичиж, дараа нь кодоо бичих арга. Red-Green-Refactor цикл.
12BDD (Behavior-Driven Development)Зан төлөвд суурилсан хөгжүүлэлтХэрэглэгчийн зан төлөвөөс эхлэн тест, код бичих. Given-When-Then загвар.
13JUnitЖи-ЮнитJava-ийн хамгийн алдартай unit тестийн фреймворк.
14Test CaseТестийн тохиолдолНэг тодорхой нөхцөлд системийн зан төлөвийг шалгах тест.
15Test SuiteТестийн багцХэд хэдэн test case-ийг бүлэглэсэн цуглуулга.
16AssertionБаталгаа / НотолгооТестийн хүлээгдэж буй үр дүнг бодит үр дүнтэй харьцуулах. assertEquals, assertTrue гэх мэт.
17Code CoverageКодын хамралтТестээр кодын хэдэн хувь хамрагдсан бэ гэдгийг хэмждэг.
18Statement CoverageМэдэгдлийн хамралтКодын мөр бүр дор хаяж 1 удаа ажиллаж байгааг шалгах.
19Branch CoverageСалаалтын хамралтif/else бүр хоёр чиглэлд тестлэгдсэн эсэхийг шалгах.
20MockХиймэл объект (Мок)Бодит объектын оронд ашигладаг хиймэл объект. Зан төлөвийг шалгахад ашигладаг.
21StubОрлуулагч (Стаб)Тогтмол хариу буцаадаг хялбар хиймэл объект.
22Bug / DefectАлдаа / СогогКодын буруу ажиллагаа. Хүлээгдэж буй үр дүнгээс зөрүү.
23Equivalence PartitioningЭквивалент ангилалОролтыг ижил зан төлөвтэй бүлгүүдэд хуваах. Бүлэг бүрээс нэг утга тестлэх.
24Boundary Value AnalysisХил заагийн шинжилгээХилийн утгууд дээр алдаа их гардаг → Хилийн утгуудыг онцгойлон тестлэх.
25Decision TableШийдвэрийн хүснэгтОлон нөхцөлийн хослолуудыг хүснэгтээр тестлэх арга.
26AAA PatternAAA загварArrange (бэлтгэх) → Act (үйлдэл хийх) → Assert (шалгах). Тестийн бүтцийн загвар.
27FIRST PrinciplesFIRST зарчимFast, Independent, Repeatable, Self-validating, Timely — Сайн тестийн 5 чанар.
28Red-Green-RefactorУлаан-Ногоон-Дахин засварTDD-ийн 3 алхам: Тест fail → Тест pass → Код цэвэрлэх.
29Test FixtureТестийн тохиргооТест ажиллуулахад шаардлагатай бэлтгэлийн өгөгдөл, объектууд.
30@TestТест аннотациJUnit-д метод нь тест гэдгийг заах аннотаци.
31@BeforeEachТест бүрийн өмнөТест бүрийн өмнө ажиллах setup метод.
32@AfterEachТест бүрийн дарааТест бүрийн дараа ажиллах teardown метод.
33@ParameterizedTestПараметрчилсэн тестОлон утгаар нэг тестийг давтах.
34assertThrowsException шалгахТодорхой exception үүсгэж байгаа эсэхийг шалгах assert метод.
35Performance TestingГүйцэтгэлийн тестСистемийн хурд, хариу өгөх хугацааг шалгах.
36Load TestingАчааллын тестОлон хэрэглэгчийн ачаалал дор системийн ажиллагааг шалгах.
37Stress TestingХүчний тестСистемийн хязгаарын ачааллыг шалгах.
38Security TestingАюулгүй байдлын тестСистемийн аюулгүй байдлыг шалгах.
39Test PyramidТестийн пирамидUnit тест олон, Integration дунд, E2E цөөн байх бүтэц.
40Continuous TestingТасралтгүй тестчилэлCI/CD дамжуулгад тест автоматаар ажиллуулах. Код push хийх бүрт тест ажиллана.