Testga asoslangan ishlab chiqish bilan kutubxonaning funksionalligini rivojlantirish

Endi biz mantiqni src/lib.rs ga chiqardik va argumentlarni yig‘ish va xatolarni qayta ishlashni src/main.rs da qoldirdik, kodimizning asosiy funksionalligi uchun testlarni yozish ancha osonlashdi. Biz turli xil argumentlar bilan funksiyalarni to'g'ridan-to'g'ri chaqirishimiz va buyruq satridan binaryga murojaat qilmasdan qaytish(return) qiymatlarini tekshirishimiz mumkin.

Ushbu bo'limda biz quyidagi bosqichlar bilan test-driven development (TDD) jarayonidan foydalangan holda minigrep dasturiga qidiruv mantig'ini qo'shamiz:

  1. Muvaffaqiyatsiz bo'lgan testni yozing va siz kutgan sabab tufayli muvaffaqiyatsiz bo'lishiga ishonch hosil qilish uchun uni ishga tushiring.
  2. Yangi testdan o'tish uchun yetarli kodni yozing yoki o'zgartiring.
  3. Siz qo'shgan yoki o'zgartirgan kodni qayta tiklang(refaktoring) va testlar o'tishda davom etayotganiga ishonch hosil qiling.
  4. Repeat from step 1!

Garchi bu dasturiy ta'minotni yozishning ko'p usullaridan biri bo'lsa-da, TDD kod dizaynini boshqarishga yordam beradi. Testdan o'tishni ta'minlaydigan kodni yozishdan oldin testni yozish jarayon davomida yuqori sinov qamrovini saqlashga yordam beradi.

Biz fayl tarkibidagi so'rovlar qatorini qidirishni amalga oshiradigan va so'rovga mos keladigan qatorlar ro'yxatini tuzadigan funksiyani amalga oshirishni sinovdan o'tkazamiz. Biz bu funksiyani qidiruv funksiyasiga qo‘shamiz.

Muvaffaqiyatsiz test yozish

Bizga endi ular kerak emasligi sababli, dasturning harakatini tekshirish uchun foydalanilgan src/lib.rs va src/main.rs dan println! statementlarini olib tashlaymiz. Keyin, src/lib.rs da, 11-bobda qilganimizdek, test funksiyasiga ega tests modulini qo'shing. Test funksiyasi biz qidirish funksiyasiga ega bo'lishini xohlagan xatti-harakatni belgilaydi: u so'rov va izlash uchun matnni oladi va u so'rovni o'z ichiga olgan matndan faqat satrlarni qaytaradi. 12-15 ro'yxatda ushbu test ko'rsatilgan, u hali kompilyatsiya bo'lmaydi.

Fayl nomi: src/lib.rs

use std::error::Error;
use std::fs;

pub struct Config {
    pub sorov: String,
    pub fayl_yoli: String,
}

impl Config {
    pub fn build(args: &[String]) -> Result<Config, &'static str> {
        if args.len() < 3 {
            return Err("argumentlar yetarli emas");
        }

        let sorov = args[1].clone();
        let fayl_yoli = args[2].clone();

        Ok(Config { sorov, fayl_yoli })
    }
}

pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
    let tarkib = fs::read_to_string(config.fayl_yoli)?;

    Ok(())
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn birinchi_natija() {
        let sorov = "marali";
        let tarkib = "\
Rust:
xavfsiz, tez, samarali.
Uchtasini tanlang.";

        assert_eq!(vec!["xavfsiz, tez, samarali."], qidiruv(sorov, tarkib));
    }
}

12-15 roʻyxat: qidiruv funksiyasi uchun muvaffaqiyatsiz test yaratish

Bu test marali qatorini qidiradi.Biz izlayotgan matn uchta qatordan iborat bo‘lib, ulardan faqat bittasi maralini o‘z ichiga oladi (E’tibor bering, qo‘sh qo‘shtirnoqning ochilishidan keyingi teskari chiziq Rustga ushbu satr literalining boshiga yangi qator belgisini qo‘ymaslikni bildiradi). qidiruv funksiyasidan qaytarilgan qiymat faqat biz kutgan qatorni o'z ichiga oladi, deb ta'kidlaymiz.

Biz hali bu testni bajara olmaymiz va uning muvaffaqiyatsizligini kuzata olmaymiz, chunki test hatto kompilyatsiya ham qilmaydi: qidiruv funksiyasi hali mavjud emas! TDD tamoyillariga muvofiq, biz 12-16 roʻyxatda koʻrsatilganidek, har doim boʻsh vektorni qaytaruvchi qidiruv funksiyasining definitionni qoʻshish orqali testni kompilyatsiya qilish va ishga tushirish uchun yetarli kodni qoʻshamiz. Keyin test kompilyatsiya qilinishi va muvaffaqiyatsiz bo'lishi kerak, chunki bo'sh vektor "xavfsiz, tez, samarali." qatorini o'z ichiga olgan vektorga mos kelmaydi.

Fayl nomi: src/lib.rs

use std::error::Error;
use std::fs;

pub struct Config {
    pub sorov: String,
    pub fayl_yoli: String,
}

impl Config {
    pub fn build(args: &[String]) -> Result<Config, &'static str> {
        if args.len() < 3 {
            return Err("argumentlar yetarli emas");
        }

        let sorov = args[1].clone();
        let fayl_yoli = args[2].clone();

        Ok(Config { sorov, fayl_yoli })
    }
}

pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
    let tarkib = fs::read_to_string(config.fayl_yoli)?;

    Ok(())
}

pub fn qidiruv<'a>(sorov: &str, tarkib: &'a str) -> Vec<&'a str> {
    vec![]
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn birinchi_natija() {
        let sorov = "marali";
        let tarkib = "\
Rust:
xavfsiz, tez, samarali.
Uchtasini tanlang.";

        assert_eq!(vec!["xavfsiz, tez, samarali."], qidiruv(sorov, tarkib));
    }
}

Ro'yxat 12-16: qidiruv funksiyasini yetarli darajada aniqlash, shuning uchun testimiz kompilyatsiya bo'ladi

E'tibor bering, biz qidiruv signaturesida 'a aniq lifetimeni belgilashimiz va bu lifetimeni tarkib argumenti va qaytarish(return) qiymati bilan ishlatishimiz kerak. 10-bobda esda tutingki, lifetime parametrlari qaysi argumentning lifetime(ishlash muddati) qaytariladigan qiymatning lifetime bilan bog'liqligini belgilaydi. Bunday holda, qaytarilgan vektorda tarkib argumentining bo'laklariga (sorov argumenti o'rniga) reference qiluvchi string bo'laklari bo'lishi kerakligini ko'rsatamiz.

Boshqacha qilib aytganda, biz Rustga aytamizki, qidiruv funksiyasi tomonidan qaytarilgan maʼlumotlar tarkib argumentida qidiruv funksiyasiga oʻtgan maʼlumotlar shuncha vaqtgacha yashaydi. Bu muhim! Murojaatlar haqiqiy bo'lishi uchun bo'laklar(slice) bo'yicha reference qilingan ma'lumotlar ham haqiqiy bo'lishi kerak; agar kompilyator biz tarkib emas, balki sorov ning satr bo'laklarini(string slice) yaratmoqda deb hisoblasa, u xavfsizlik tekshiruvini noto'g'ri bajaradi.

Agar biz lifetime izohlarni(annotation) unutib, ushbu funksiyani kompilyatsiya qilishga harakat qilsak, biz ushbu xatoni olamiz:

$ cargo build
   Compiling minigrep v0.1.0 (file:///projects/minigrep)
error[E0106]: missing lifetime specifier
  --> src/lib.rs:29:50
   |
29 | pub fn qidiruv(sorov: &str, tarkib: &str) -> Vec<&str> {
   |                       ----          ----         ^ expected named lifetime parameter
   |
   = help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `sorov` or `tarkib`
help: consider introducing a named lifetime parameter
   |
29 | pub fn qidiruv<'a>(sorov: &'a str, tarkib: &'a str) -> Vec<&'a str> {
   |               ++++         ++               ++              ++

For more information about this error, try `rustc --explain E0106`.
error: could not compile `minigrep` (lib) due to previous error
warning: build failed, waiting for other jobs to finish...
error: could not compile `minigrep` (lib test) due to previous error

Rust bizga ikkita argumenning qaysi biri kerakligini bila olmaydi, shuning uchun biz buni aniq aytishimiz kerak. tarkib barcha matnimizni o'z ichiga olgan argument bo'lgani uchun va biz ushbu matnning mos keladigan qismlarini qaytarmoqchi bo'lganimiz sababli, biz tarkib lifetime sintaksisi yordamida qaytarish qiymatiga ulanishi kerak bo'lgan argument ekanligini bilamiz.

Boshqa dasturlash tillari signaturedagi qiymatlarni qaytarish uchun argumentlarni ulashni talab qilmaydi, ammo bu amaliyot vaqt o'tishi bilan osonlashadi. Siz ushbu misolni 10-bobdagi “Ma’lumotnomalarni lifetime bilan tekshirish” bo‘limi bilan solishtirishingiz mumkin.

Endi testni bajaramiz:

$ cargo test -- --show-output
   Compiling silly-function v0.1.0 (file:///projects/silly-function)
    Finished test [unoptimized + debuginfo] target(s) in 0.60s
     Running unittests src/lib.rs (target/debug/deps/silly_function-160869f38cff9166)

running 1 test
test tests::birinchi_natija ... FAILED

successes:

successes:

failures:

---- tests::birinchi_natija stdout ----
thread 'tests::birinchi_natija' panicked at 'assertion failed: `(left == right)`
  left: `["xavfsiz, tez, samarali."]`,
 right: `[]`', src/lib.rs:46:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace


failures:
    tests::birinchi_natija

test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

error: test failed, to rerun pass `--lib`

Ajoyib, test biz kutganimizdek muvaffaqiyatsiz tugadi. Keling, testdan o'tamiz!

Testdan o'tish uchun kod yozish

Hozirda testimiz muvaffaqiyatsiz tugadi, chunki biz har doim bo'sh vektorni qaytaramiz. Buni tuzatish va qidiruv ni amalga oshirish uchun dasturimiz quyidagi bosqichlarni bajarishi kerak:

  • tarkib ning har bir satrini takrorlang.
  • Berilgan satrda siz izlayotgan qator mavjudligini tekshiring.
  • Agar shunday bo'lsa, uni biz qaytaradigan qiymatlar ro'yxatiga qo'shing.
  • Agar bunday bo'lmasa, hech narsa qilmang.
  • Mos keladigan natijalar ro'yxatini qaytaring.

Keling, satrlarni takrorlashdan boshlab, har bir bosqichda ishlaylik.

lines metodi bilan qatorlar bo'ylab takrorlash

Rust 12-17 ro'yxatda ko'rsatilganidek, qulay tarzda lines deb nomlangan satrlarni qatorma-qator takrorlash uchun foydali metodga ega. E'tibor bering, bu hali kompilyatsiya qilinmaydi.

Fayl nomi: src/lib.rs

use std::error::Error;
use std::fs;

pub struct Config {
    pub sorov: String,
    pub fayl_yoli: String,
}

impl Config {
    pub fn build(args: &[String]) -> Result<Config, &'static str> {
        if args.len() < 3 {
            return Err("argumentlar yetarli emas");
        }

        let sorov = args[1].clone();
        let fayl_yoli = args[2].clone();

        Ok(Config { sorov, fayl_yoli })
    }
}

pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
    let tarkib = fs::read_to_string(config.fayl_yoli)?;

    Ok(())
}

pub fn qidiruv<'a>(sorov: &str, tarkib: &'a str) -> Vec<&'a str> {
    for line in tarkib.lines() {
        // do something with line
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn birinchi_natija() {
        let sorov = "marali";
        let tarkib = "\
Rust:
xavfsiz, tez, samarali.
Uchtasini tanlang.";

        assert_eq!(vec!["xavfsiz, tez, samarali."], qidiruv(sorov, tarkib));
    }
}

Ro'yxat 12-17: tarkibdagi har bir qatorni takrorlash

lines metodi iteratorni qaytaradi.Biz iteratorlar haqida 13-bobda chuqurroq gaplashamiz, lekin esda tutingki, siz iteratordan foydalanishning bunday usulini 3-5-ro'yxatda ko'rgansiz, bu yerda biz to'plamdagi har bir elementda ba'zi kodlarni ishlatish uchun iterator bilan for siklidan foydalanganmiz.

So'rov uchun har bir qatorni qidirish

Keyinchalik, joriy qatorda so'rovlar qatori mavjudligini tekshiramiz. Yaxshiyamki, satrlarda biz uchun buni amalga oshiradigan contains deb nomlangan foydali metod mavjud! 12-18 roʻyxatda koʻrsatilganidek, qidiruv funksiyasidagi contains metodiga murojatni qoʻshing. E'tibor bering, bu hali kompilyatsiya qilinmaydi.

Fayl nomi: src/lib.rs

use std::error::Error;
use std::fs;

pub struct Config {
    pub sorov: String,
    pub fayl_yoli: String,
}

impl Config {
    pub fn build(args: &[String]) -> Result<Config, &'static str> {
        if args.len() < 3 {
            return Err("argumentlar yetarli emas");
        }

        let sorov = args[1].clone();
        let fayl_yoli = args[2].clone();

        Ok(Config { sorov, fayl_yoli })
    }
}

pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
    let tarkib = fs::read_to_string(config.fayl_yoli)?;

    Ok(())
}

pub fn qidiruv<'a>(sorov: &str, tarkib: &'a str) -> Vec<&'a str> {
    for line in tarkib.lines() {
        if line.contains(sorov) {
            // do something with line
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn birinchi_natija() {
        let sorov = "marali";
        let tarkib = "\
Rust:
xavfsiz, tez, samarali.
Uchtasini tanlang.";

        assert_eq!(vec!["xavfsiz, tez, samarali."], qidiruv(sorov, tarkib));
    }
}

Ro'yxat 12-18: satrda sorov dagi satr mavjudligini ko'rish uchun funksiya qo'shiladi

Ayni paytda biz funksionallikni yaratmoqdamiz. Uni kompilyatsiya qilish uchun biz funksiya signaturesida ko'rsatganimizdek, tanadan qiymatni qaytarishimiz kerak.

Mos keladigan qatorlarni saqlash

Ushbu funksiyani tugatish uchun bizga qaytarmoqchi bo'lgan mos keladigan satrlarni saqlash metodi kerak. Buning uchun biz for siklidan oldin o'zgaruvchan vector yasashimiz va vectorda lineni saqlash uchun push metodini chaqirishimiz mumkin. for siklidan so'ng, 12-19 ro'yxatda ko'rsatilganidek, vectorni qaytaramiz.

Fayl nomi: src/lib.rs

use std::error::Error;
use std::fs;

pub struct Config {
    pub sorov: String,
    pub fayl_yoli: String,
}

impl Config {
    pub fn build(args: &[String]) -> Result<Config, &'static str> {
        if args.len() < 3 {
            return Err("argumentlar yetarli emas");
        }

        let sorov = args[1].clone();
        let fayl_yoli = args[2].clone();

        Ok(Config { sorov, fayl_yoli })
    }
}

pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
    let tarkib = fs::read_to_string(config.fayl_yoli)?;

    Ok(())
}

pub fn qidiruv<'a>(sorov: &str, tarkib: &'a str) -> Vec<&'a str> {
    let mut natijalar = Vec::new();

    for line in tarkib.lines() {
        if line.contains(sorov) {
            natijalar.push(line);
        }
    }

    natijalar
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn birinchi_natija() {
        let sorov = "marali";
        let tarkib = "\
Rust:
xavfsiz, tez, samarali.
Uchtasini tanlang.";

        assert_eq!(vec!["xavfsiz, tez, samarali."], qidiruv(sorov, tarkib));
    }
}

Ro'yxat 12-19: Biz ularni qaytarishimiz uchun mos keladigan satrlarni saqlash

Endi qidiruv funksiyasi faqat sorov ni o'z ichiga olgan qatorlarni qaytarishi kerak va bizning testimiz o'tishi kerak. Keling, testni bajaramiz:

$ cargo test
   Compiling minigrep v0.1.0 (file:///projects/minigrep)
    Finished test [unoptimized + debuginfo] target(s) in 0.37s
     Running unittests src/lib.rs (target/debug/deps/minigrep-9cd200e5fac0fc94)

running 1 test
test tests::birinchi_natija ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

     Running unittests src/main.rs (target/debug/deps/minigrep-54f36c611e701f9d)

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

   Doc-tests minigrep

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

Testimiz muvaffaqiyatli o'tdi, shuning uchun u ishlayotganini bilamiz!

Shu nuqtada, biz bir xil funksionallikni saqlab qolish uchun testlarni o'tkazgan holda qidiruv funksiyasini amalga oshirishni qayta tiklash imkoniyatlarini ko'rib chiqishimiz mumkin. Qidiruv funksiyasidagi kod juda yomon emas, lekin u iteratorlarning ba'zi foydali xususiyatlaridan foydalanmaydi. Biz 13-bobda ushbu misolga qaytamiz, u yerda iteratorlarni batafsil o'rganamiz va uni qanday yaxshilashni ko'rib chiqamiz.

run funksiyasidagi qidiruv funksiyasidan foydalanish

Endi qidiruv funksiyasi ishlayotgan va testdan o‘tgan bo‘lsa, run funksiyamizdan qidiruv ni chaqirishimiz kerak. Biz config.sorov qiymatini va fayldan o'qiydigan tarkib-ni qidiruv funksiyasiga o'tkazishimiz kerak. Keyin run qidiruvdan qaytarilgan har bir qatorni chop etadi:

Fayl nomi: src/lib.rs

use std::error::Error;
use std::fs;

pub struct Config {
    pub sorov: String,
    pub fayl_yoli: String,
}

impl Config {
    pub fn build(args: &[String]) -> Result<Config, &'static str> {
        if args.len() < 3 {
            return Err("argumentlar yetarli emas");
        }

        let sorov = args[1].clone();
        let fayl_yoli = args[2].clone();

        Ok(Config { sorov, fayl_yoli })
    }
}

pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
    let tarkib = fs::read_to_string(config.fayl_yoli)?;

    for line in qidiruv(&config.sorov, &tarkib) {
        println!("{line}");
    }

    Ok(())
}

pub fn qidiruv<'a>(sorov: &str, tarkib: &'a str) -> Vec<&'a str> {
    let mut natijalar = Vec::new();

    for line in tarkib.lines() {
        if line.contains(sorov) {
            natijalar.push(line);
        }
    }

    natijalar
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn birinchi_natija() {
        let sorov = "marali";
        let tarkib = "\
Rust:
xavfsiz, tez, samarali.
Uchtasini tanlang.";

        assert_eq!(vec!["xavfsiz, tez, samarali."], qidiruv(sorov, tarkib));
    }
}

Biz qidiruv dan har bir qatorni qaytarish va uni chop etish uchun for siklidan foydalanmoqdamiz.

Endi butun dastur ishlashi kerak! Keling, buni sinab ko'raylik, avval Olma she'ridagi "karnay" ning aynan bir satrini qaytarishi kerak bo'lgan so'z bilan:

$ cargo run -- karnay olma.txt
   Compiling minigrep v0.1.0 (file:///projects/minigrep)
    Finished dev [unoptimized + debuginfo] target(s) in 0.01s
     Running `target/debug/minigrep karnay olma.txt`
Ishtahang bo'lsin karnay

Ajoyib! Keling, bir nechta qatorga mos keladigan so'zni sinab ko'raylik, masalan, "olma":

$ cargo run -- olma olma.txt
   Compiling minigrep v0.1.0 (file:///projects/minigrep)
    Finished dev [unoptimized + debuginfo] target(s) in 0.38s
     Running `target/debug/minigrep olma olma.txt`
Tanishaylik, men - olma,
Nomimga quloq solma.
Men sizlarni olmangiz,
Xomligimda olmangiz!
Voy qornim deb qolmangiz!

Va nihoyat, she’rning hech bir joyida bo‘lmagan so‘zni izlaganimizda, masalan, “mashina” kabi satrlar chiqmasligiga ishonch hosil qilaylik:

$ cargo run -- mashina olma.txt
   Compiling minigrep v0.1.0 (file:///projects/minigrep)
    Finished dev [unoptimized + debuginfo] target(s) in 0.01s
     Running `target/debug/minigrep mashina olma.txt`

Ajoyib! Biz klassik dasturning o'z mini versiyasini yaratdik va ilovalarni qanday tuzish haqida ko'p narsalarni o'rgandik. Shuningdek, biz faylni kiritish(input) va chiqarish(output), lifetime, test va buyruq satrini tahlil qilish haqida bir oz o'rgandik.

Ushbu loyihani yakunlash uchun biz atrof-muhit(environment) o'zgaruvchilari bilan qanday ishlashni va standart xatoga qanday chop etishni qisqacha ko'rsatamiz, bu ikkalasi ham buyruq qatori dasturlarini yozishda foydalidir..