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:
- Muvaffaqiyatsiz bo'lgan testni yozing va siz kutgan sabab tufayli muvaffaqiyatsiz bo'lishiga ishonch hosil qilish uchun uni ishga tushiring.
- Yangi testdan o'tish uchun yetarli kodni yozing yoki o'zgartiring.
- Siz qo'shgan yoki o'zgartirgan kodni qayta tiklang(refaktoring) va testlar o'tishda davom etayotganiga ishonch hosil qiling.
- 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));
}
}
Bu test marali
qatorini qidiradi.Biz izlayotgan matn uchta qatordan iborat bo‘lib, ulardan faqat bittasi marali
ni 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));
}
}
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));
}
}
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));
}
}
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 line
ni 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));
}
}
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
qidiruv
dan 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..