ЛАБ 03

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

3.1 Лабораторийн зорилго

Энэ лабораторид та JUnit 5 ашиглан unit тест бичих чадвар эзэмшинэ:

  1. JUnit 5 суулгах ба анхны тест бичих
  2. Хил заагийн шинжилгээ (Boundary Value) ашиглан тестлэх
  3. Exception тестлэх
  4. TDD аргаар шинэ функц хөгжүүлэх

Шаардлагатай зүйлс: Java JDK 17+, Eclipse IDE, Maven

3.2 Лаб 1: JUnit 5 суулгах ба анхны тест

Алхам 1: Eclipse-д шинэ Maven төсөл үүсгэнэ (File → New → Maven Project): TestingLab

Алхам 2: pom.xml файлд JUnit 5 хамаарлыг нэмнэ:

<dependencies>
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter</artifactId>
        <version>5.10.2</version>
        <scope>test</scope>
    </dependency>
</dependencies>

Алхам 3: src/main/java дотор StringUtils.java үүсгэнэ:

public class StringUtils {

    // Тэмдэгт мөрийг урвуулах
    public static String reverse(String str) {
        if (str == null) {
            throw new IllegalArgumentException("null утга оруулж болохгүй");
        }
        return new StringBuilder(str).reverse().toString();
    }

    // Palindrome мөн эсэхийг шалгах (урд хойноосоо адилхан)
    public static boolean isPalindrome(String str) {
        if (str == null) {
            throw new IllegalArgumentException("null утга оруулж болохгүй");
        }
        String cleaned = str.toLowerCase().replaceAll("\\s+", "");
        return cleaned.equals(new StringBuilder(cleaned).reverse().toString());
    }

    // Үгийн тоог тоолох
    public static int countWords(String str) {
        if (str == null || str.trim().isEmpty()) {
            return 0;
        }
        return str.trim().split("\\s+").length;
    }

    // Эхний үсгийг том болгох
    public static String capitalize(String str) {
        if (str == null || str.isEmpty()) {
            return str;
        }
        return str.substring(0, 1).toUpperCase() + str.substring(1);
    }
}

Алхам 4: src/test/java дотор StringUtilsTest.java үүсгэнэ:

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

class StringUtilsTest {

    // === reverse() тестүүд ===

    @Test
    @DisplayName("Энгийн мөр урвуулах")
    void reverse_normalString_returnsReversed() {
        assertEquals("табА", StringUtils.reverse("Абат"));
    }

    @Test
    @DisplayName("Хоосон мөр урвуулах")
    void reverse_emptyString_returnsEmpty() {
        assertEquals("", StringUtils.reverse(""));
    }

    @Test
    @DisplayName("null оруулахад exception үүсэх")
    void reverse_null_throwsException() {
        assertThrows(IllegalArgumentException.class, () -> {
            StringUtils.reverse(null);
        });
    }

    // === isPalindrome() тестүүд ===

    @Test
    @DisplayName("'madam' нь palindrome")
    void isPalindrome_madam_returnsTrue() {
        assertTrue(StringUtils.isPalindrome("madam"));
    }

    @Test
    @DisplayName("'hello' нь palindrome биш")
    void isPalindrome_hello_returnsFalse() {
        assertFalse(StringUtils.isPalindrome("hello"));
    }

    @Test
    @DisplayName("Хоосон мөр palindrome мөн")
    void isPalindrome_empty_returnsTrue() {
        assertTrue(StringUtils.isPalindrome(""));
    }

    @Test
    @DisplayName("Зайтай palindrome шалгах")
    void isPalindrome_withSpaces_returnsTrue() {
        assertTrue(StringUtils.isPalindrome("A man a plan a canal Panama"));
    }

    // === countWords() тестүүд ===

    @Test
    @DisplayName("3 үгтэй мөрийн үг тоолох")
    void countWords_threeWords_returns3() {
        assertEquals(3, StringUtils.countWords("Сайн байна уу"));
    }

    @Test
    @DisplayName("Хоосон мөрийн үг тоолох")
    void countWords_empty_returns0() {
        assertEquals(0, StringUtils.countWords(""));
    }

    @Test
    @DisplayName("null мөрийн үг тоолох")
    void countWords_null_returns0() {
        assertEquals(0, StringUtils.countWords(null));
    }

    // === capitalize() тестүүд ===

    @Test
    @DisplayName("Эхний үсгийг том болгох")
    void capitalize_normal_returnsCapitalized() {
        assertEquals("Hello", StringUtils.capitalize("hello"));
    }

    @Test
    @DisplayName("Хоосон мөр capitalize")
    void capitalize_empty_returnsEmpty() {
        assertEquals("", StringUtils.capitalize(""));
    }
}

Алхам 5: Eclipse дээр тестийг ажиллуулна:

  • StringUtilsTest класс дээр баруун товч → Run As → JUnit Test дарна
  • Бүх тест ногоон (✅) байх ёстой

3.3 Лаб 2: Хил заагийн шинжилгээ — Дүнгийн баталгаажуулалт

Даалгавар: Оюутны дүнг 0-100 хооронд шалгаж, үсгэн үнэлгээ (A-F) буцаах системийг тестлэх.

// ===== GradeCalculator.java =====
public class GradeCalculator {

    public String getGrade(int score) {
        if (score < 0 || score > 100) {
            throw new IllegalArgumentException("Дүн 0-100 хооронд байх ёстой. Оруулсан: " + score);
        }

        if (score >= 90) return "A";     // 90-100
        if (score >= 80) return "B";     // 80-89
        if (score >= 70) return "C";     // 70-79
        if (score >= 60) return "D";     // 60-69
        return "F";                       // 0-59
    }

    public boolean isPassing(int score) {
        String grade = getGrade(score);
        return !grade.equals("F");        // F биш бол тэнцсэн
    }
}
// ===== GradeCalculatorTest.java — Хил заагийн тест =====
import org.junit.jupiter.api.*;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.junit.jupiter.params.provider.ValueSource;
import static org.junit.jupiter.api.Assertions.*;

class GradeCalculatorTest {

    private GradeCalculator calculator;

    @BeforeEach
    void setUp() {
        calculator = new GradeCalculator();
    }

    // === Хил заагийн тестүүд (Boundary Value Analysis) ===

    @Test
    @DisplayName("Хилийн утга: 0 → F")
    void getGrade_score0_returnsF() {
        assertEquals("F", calculator.getGrade(0));
    }

    @Test
    @DisplayName("Хилийн утга: 59 → F")
    void getGrade_score59_returnsF() {
        assertEquals("F", calculator.getGrade(59));
    }

    @Test
    @DisplayName("Хилийн утга: 60 → D")
    void getGrade_score60_returnsD() {
        assertEquals("D", calculator.getGrade(60));
    }

    @Test
    @DisplayName("Хилийн утга: 69 → D")
    void getGrade_score69_returnsD() {
        assertEquals("D", calculator.getGrade(69));
    }

    @Test
    @DisplayName("Хилийн утга: 70 → C")
    void getGrade_score70_returnsC() {
        assertEquals("C", calculator.getGrade(70));
    }

    @Test
    @DisplayName("Хилийн утга: 79 → C")
    void getGrade_score79_returnsC() {
        assertEquals("C", calculator.getGrade(79));
    }

    @Test
    @DisplayName("Хилийн утга: 80 → B")
    void getGrade_score80_returnsB() {
        assertEquals("B", calculator.getGrade(80));
    }

    @Test
    @DisplayName("Хилийн утга: 89 → B")
    void getGrade_score89_returnsB() {
        assertEquals("B", calculator.getGrade(89));
    }

    @Test
    @DisplayName("Хилийн утга: 90 → A")
    void getGrade_score90_returnsA() {
        assertEquals("A", calculator.getGrade(90));
    }

    @Test
    @DisplayName("Хилийн утга: 100 → A")
    void getGrade_score100_returnsA() {
        assertEquals("A", calculator.getGrade(100));
    }

    // === Exception тестүүд ===

    @Test
    @DisplayName("Сөрөг дүн → Exception")
    void getGrade_negativeScore_throwsException() {
        assertThrows(IllegalArgumentException.class, () -> {
            calculator.getGrade(-1);
        });
    }

    @Test
    @DisplayName("101 дүн → Exception")
    void getGrade_score101_throwsException() {
        assertThrows(IllegalArgumentException.class, () -> {
            calculator.getGrade(101);
        });
    }

    // === Параметрчилсэн тест (олон утгаар нэг дор тестлэх) ===

    @ParameterizedTest
    @DisplayName("Олон дүнгээр тестлэх")
    @CsvSource({
        "0, F",    "30, F",   "59, F",
        "60, D",   "65, D",   "69, D",
        "70, C",   "75, C",   "79, C",
        "80, B",   "85, B",   "89, B",
        "90, A",   "95, A",   "100, A"
    })
    void getGrade_variousScores_returnsCorrectGrade(int score, String expectedGrade) {
        assertEquals(expectedGrade, calculator.getGrade(score));
    }

    // === isPassing() тест ===

    @ParameterizedTest
    @DisplayName("Тэнцсэн дүнгүүд")
    @ValueSource(ints = {60, 70, 80, 90, 100})
    void isPassing_passingScores_returnsTrue(int score) {
        assertTrue(calculator.isPassing(score));
    }

    @ParameterizedTest
    @DisplayName("Тэнцээгүй дүнгүүд")
    @ValueSource(ints = {0, 30, 59})
    void isPassing_failingScores_returnsFalse(int score) {
        assertFalse(calculator.isPassing(score));
    }
}

3.4 Лаб 3: TDD аргаар Password Validator хөгжүүлэх

Даалгавар: Нууц үгийн хүчтэй байдлыг шалгах PasswordValidator классыг TDD аргаар бичих.

Шаардлага:

  • Нууц үг дор хаяж 8 тэмдэгттэй
  • Дор хаяж 1 том үсэг
  • Дор хаяж 1 жижиг үсэг
  • Дор хаяж 1 тоо
  • Дор хаяж 1 тусгай тэмдэгт (!@#$%^&*)

Алхам 1 — 🔴 RED: Тестийг эхлээд бичнэ

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import static org.junit.jupiter.api.Assertions.*;

class PasswordValidatorTest {

    private PasswordValidator validator = new PasswordValidator();

    @Test
    @DisplayName("null нууц үг → false")
    void validate_null_returnsFalse() {
        assertFalse(validator.isValid(null));
    }

    @Test
    @DisplayName("Хоосон нууц үг → false")
    void validate_empty_returnsFalse() {
        assertFalse(validator.isValid(""));
    }

    @Test
    @DisplayName("7 тэмдэгт → богино → false")
    void validate_tooShort_returnsFalse() {
        assertFalse(validator.isValid("Ab1!xyz"));       // 7 тэмдэгт
    }

    @Test
    @DisplayName("Том үсэггүй → false")
    void validate_noUpperCase_returnsFalse() {
        assertFalse(validator.isValid("abcdef1!"));
    }

    @Test
    @DisplayName("Жижиг үсэггүй → false")
    void validate_noLowerCase_returnsFalse() {
        assertFalse(validator.isValid("ABCDEF1!"));
    }

    @Test
    @DisplayName("Тоогүй → false")
    void validate_noDigit_returnsFalse() {
        assertFalse(validator.isValid("Abcdefg!"));
    }

    @Test
    @DisplayName("Тусгай тэмдэгтгүй → false")
    void validate_noSpecialChar_returnsFalse() {
        assertFalse(validator.isValid("Abcdefg1"));
    }

    @Test
    @DisplayName("Бүх шаардлага хангасан → true")
    void validate_validPassword_returnsTrue() {
        assertTrue(validator.isValid("MyPass1!"));
    }

    @ParameterizedTest
    @DisplayName("Хүчтэй нууц үгүүд")
    @ValueSource(strings = {"Hello@123", "Str0ng!Pass", "Test#2024x"})
    void validate_strongPasswords_returnTrue(String password) {
        assertTrue(validator.isValid(password));
    }
}

Алхам 2 — 🟢 GREEN: Тестийг дамжуулах кодыг бичнэ

public class PasswordValidator {

    public boolean isValid(String password) {
        // null эсвэл хоосон → буруу
        if (password == null || password.isEmpty()) {
            return false;
        }

        // 8-аас бага тэмдэгт → буруу
        if (password.length() < 8) {
            return false;
        }

        boolean hasUpper = false;    // Том үсэг
        boolean hasLower = false;    // Жижиг үсэг
        boolean hasDigit = false;    // Тоо
        boolean hasSpecial = false;  // Тусгай тэмдэгт

        for (char c : password.toCharArray()) {
            if (Character.isUpperCase(c)) hasUpper = true;
            else if (Character.isLowerCase(c)) hasLower = true;
            else if (Character.isDigit(c)) hasDigit = true;
            else hasSpecial = true;
        }

        return hasUpper && hasLower && hasDigit && hasSpecial;
    }

    // Нууц үгийн хүч тооцоолох (нэмэлт)
    public String getStrength(String password) {
        if (!isValid(password)) return "Сул";
        if (password.length() >= 12) return "Хүчтэй";
        return "Дунд";
    }
}

Алхам 3 — 🔵 REFACTOR: Тест бүгд ногоон → Код цэвэрлэх

Бүх тест ажиллуулж, бүгд ногоон (✅) болсон эсэхийг шалгана. Шаардлагатай бол кодыг цэвэрлэнэ.


3.5 Лаб 4: Mock ашиглах жишээ (Mockito)

Даалгавар: Имэйл илгээх системийг Mock ашиглан тестлэх.

Алхам 1: pom.xml-д Mockito нэмэх:

<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-core</artifactId>
    <version>5.11.0</version>
    <scope>test</scope>
</dependency>

Алхам 2: Код бичих:

// ===== EmailService интерфейс =====
interface EmailService {
    boolean sendEmail(String to, String subject, String body);
}

// ===== UserRegistration класс =====
class UserRegistration {
    private EmailService emailService;

    public UserRegistration(EmailService emailService) {
        this.emailService = emailService;       // Dependency injection
    }

    public boolean register(String name, String email) {
        if (name == null || name.isEmpty()) return false;
        if (email == null || !email.contains("@")) return false;

        // Бүртгэл амжилттай → Баталгаажуулах имэйл илгээх
        String subject = "Бүртгэл амжилттай!";
        String body = "Сайн байна уу, " + name + "! Таны бүртгэл амжилттай.";
        emailService.sendEmail(email, subject, body);

        return true;
    }
}

Алхам 3: Mock тест бичих:

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.DisplayName;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;

class UserRegistrationTest {

    @Test
    @DisplayName("Зөв мэдээлэлтэй бүртгэл → имэйл илгээгдэнэ")
    void register_validUser_sendsEmail() {
        // ARRANGE — Mock EmailService үүсгэх
        EmailService mockEmail = mock(EmailService.class);
        when(mockEmail.sendEmail(anyString(), anyString(), anyString()))
            .thenReturn(true);

        UserRegistration reg = new UserRegistration(mockEmail);

        // ACT — Бүртгэл хийх
        boolean result = reg.register("Бат", "bat@email.com");

        // ASSERT — Бүртгэл амжилттай
        assertTrue(result);

        // VERIFY — Имэйл илгээгдсэн эсэхийг шалгах
        verify(mockEmail, times(1)).sendEmail(
            eq("bat@email.com"),        // Зөв имэйл хаяг руу
            eq("Бүртгэл амжилттай!"),  // Зөв гарчигтай
            contains("Бат")             // Нэр агуулсан
        );
    }

    @Test
    @DisplayName("Буруу имэйлтэй бүртгэл → имэйл илгээгдэхгүй")
    void register_invalidEmail_noEmailSent() {
        EmailService mockEmail = mock(EmailService.class);
        UserRegistration reg = new UserRegistration(mockEmail);

        boolean result = reg.register("Бат", "invalid-email");

        assertFalse(result);
        // Имэйл огт дуудагдаагүй байх ёстой
        verify(mockEmail, never()).sendEmail(anyString(), anyString(), anyString());
    }

    @Test
    @DisplayName("Нэр хоосон → бүртгэл амжилтгүй")
    void register_emptyName_returnsFalse() {
        EmailService mockEmail = mock(EmailService.class);
        UserRegistration reg = new UserRegistration(mockEmail);

        assertFalse(reg.register("", "bat@email.com"));
        assertFalse(reg.register(null, "bat@email.com"));
        verify(mockEmail, never()).sendEmail(anyString(), anyString(), anyString());
    }
}

---
---