[Learn Rust together · Project Combat] Command line IO project minigrep - test-driven development to improve functions


foreword

After the study in the previous three sections, our small tool minigrep has realized the reading of the specified file content, and for the convenience of later development and testing, the entire project has been restructured to standardize error handling and standardize modules. This time we will use 测试驱动开发(以后简称TDD)the same pattern for development, write several program test cases for the program, and test the function of the program to search the query string and return the matching row examples, which will be used in the later development process.

Test-Driven Development, English full name Test-Driven Development, referred to as TDD, is a new development method different from the traditional software development process. It requires to write the test code before writing the code of a certain function, and then only write the function code that makes the test pass, and promote the whole development through the test. This helps to write clean, usable and high-quality code and speeds up the development process.


1. Task purpose

Understand 测试驱动开发模式(TDD)and be familiar with its development steps. Use the TDD development model to write the test function code we need, and gradually increase the function of the software.

TDD is a software development technique that follows the following steps:

  1. Write a failing test, and run it to make sure it fails for what you expect.
  2. Write or modify enough code to make the new test pass.
  3. Refactor the code that was just added or modified and make sure the tests still pass.
  4. Repeat from step 1!

Benefits of using the TDD development model

  1. Helps drive code design
  2. Helps maintain high test coverage during development

2. Write test failure cases

1. Add test modules and test functions

We imitated the test code that comes with the library when we created it, and wrote the test module, in which we wrote a one_resultfunction for testing, which defined the query search keywords and contents, simulated the parameters obtained in our actual operation, and called A searchfunction that passes in the parameters just now and 断言returns the vector of the line with the keyword.

The keyword we pass in here is 芙蓉, so if searchit works properly, it will return 芙蓉老秋霜,团扇羞网尘。戚姬髡发入舂市,万古共悲辛。.

searchThe function has not been written yet, so direct compilation will inevitably report an error. Here we hope to pass in these two values ​​​​and return the line where the keyword is located. The searchfunction is written according to the way we call it.

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

#[test]
fn one_result() {
    
    
    let query = "芙蓉";
    let contents = "\
中山孺子妾,特以色见珍。虽然不如延年妹,亦是当时绝世人。
桃李出深井,花艳惊上春。一贵复一贱,关天岂由身。
芙蓉老秋霜,团扇羞网尘。戚姬髡发入舂市,万古共悲辛。";

    assert_eq!(vec!["芙蓉老秋霜,团扇羞网尘。戚姬髡发入舂市,万古共悲辛。"], search(query, contents));
    }
}

2. Write the search function

Since we are writing a test error use case here, we need to ensure that the program error is in the way we expect, so here we return an empty vector in the search function to ensure that the code can be compiled and the result returned is not what we expected. code show as below,

pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
    
    
    vec![]
}

At this point, we run the test, and the result returns an assertion 左值不等于右值, indicating that the code we wrote is correct. We will fix this error later and let the code test pass, as shown in the figure below

3. Modify the code and let the code test pass

Currently the test fails because we always return an empty vector. In order for the program to pass the test, we need to improve searchthe logic of the function and return the correct result. searchThe program flow chart is as follows

Created with Raphaël 2.3.0 开始 定义存储Vector 是否到达尾行? 结束 是否包含关键词 yes no

1. Read by row

Rust provides a method that can read text by line lines, and his calling method is

contents.lines()

This method returns an array where each element is a line of text content. We use a for loop to read each line and operate on each line, so searchchange the function like this

pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
    
    
    for line in contents.lines() {
    
    
        // 对文本行进行操作
    }
}

2. Check keywords

Checking keywords is actually looking for strings, and Rust strings also provide a method for looking up strings contains, which is how it is called

contents.contains(keyword)

Now we add it searchto the function

pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
    
    
        for line in contents.lines() {
    
    
            // 对文本行进行操作
            if line.contains(query) {
    
    

            }
        }
    }

3. Store search results

Now we can traverse each row and check each row to see if the keyword we are looking for exists, so what we need to consider now is how to save and return these rows containing keywords. Consider creating a Vector outside the for loop, and whenever there is a qualifying row, add it to the judgment of the for loop, the code is as follows

pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
    
    
    let mut results = Vec::new();

    for line in contents.lines() {
    
    
        if line.contains(query) {
    
    
            results.push(line);
        }
    }

    results
}

A variable Vector type variable is defined here results, and then judged in the for loop, if there is a row that meets the conditions, add this row resultsto it, and finally return results.

4. Run the tests

Now let's run this test case.

It can be seen that the function we wrote searchmeets the conditions and passes the test.

Fourth, use the code in the program

The main logic of our project is placed in runthe function, so we only need to runcall searchthe function in the function and output the content of each line. The following is the code

pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
    
    
    let contents = fs::read_to_string(config.filename)?;
    for line in search(&config.query, &contents){
    
    
        println!("{}", line);
    }
    Ok(())
}

At this time, run the program to see the effect,

enter a relatively short keyword, check whether all lines can be found,

enter a keyword that does not exist in it


Summarize

Now we have basically completed the development of this small tool, created a small tool of our own, learned how to organize programs, the development method of driving test development, and some file input and output, life cycle, testing and command line analysis content.

So far, the main functions of this small tool have been developed, and we will optimize the processing of environment variables and output standard content in the future, to be continued.

Operation

So far you have basically completed this small case, please think about the following:

  • For string operations, such as string segmentation, string replacement, etc., how to use Rust to write.
  • What are the advantages of test-driven development and what are the steps.

Guess you like

Origin blog.csdn.net/weixin_47754149/article/details/125761143