Testing with Rust

Adding unit tests to the wordle application

Following up on my previous article I would like to explore unit testing in the context of my wordle application. I want to add tests to the functions I created as well as understand how to test user input in the console.

First up I will move my test to a module so they are isolated from the rest of the code. This test module is created by using the macro cfg which stands for configuration and will instruct Rust to only include the content inside then the corresponding configuration option is provided. You can read more in depth details about this here.

The change to the code looks like this:

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

    #[test]
    fn test_choose_word() {
        let word = choose_word();
        assert_eq!(word.chars().count(), 5);
    }
}

Because choose_word is a private function we have to use super::choose_word to allow the test module to use the function. You don't have to test private functions but if you don't want to make them public, this is the way to make sure they are available for your test functions.

The assert_eq function checks that the first parameter matches whats given in the second parameter and if not it will fail the test.

Now let's add another test for does_character_exist. Here we want to check that we get true if the given character exists and false if it doesn't. First we change the super reference to access all functions in the parent scope.

se super::*;

Then we add the test:

 #[test]
fn test_does_character_exist() {
    let word = "apple";
    assert_eq!(does_character_exist('a', word), true);
    assert_eq!(does_character_exist('b', word), false);
}

Now we can use cargo test to run the tests and confirm that both tests pass. Since we have this test working lets add one for is_position_correct.

#[test]
fn test_is_position_correct() {
    let word = "apple";
    assert_eq!(is_position_correct('l', 3, word), true);
    assert_eq!(is_position_correct('p', 0, word), false);
}

Again run cargo test to confirm that the test is parsing. We have so far been testing fairly simple use cases, now lets add a test for check_word_correct which will return a Vec of the characters in the word.

In order to do this we first have to modify the CharState enum to allow the test function to compare the returned value with the enum options. We have to add Debug to the derive macro. The explanation of debug can be found here.

The derive attribute automatically creates the implementation required to make this struct printable with fmt::Debug.

#[derive(PartialEq, Debug)]
enum CharState {
    Correct,
    Wrong,
    Exists,
}

Now that we have made that change lets add a test for a matching word.

#[test]
fn test_check_word_correct_correct() {
    let word = "brown";
    let correct = check_word_correct("brown", word);
    assert_eq!(correct[0].value, CharState::Correct);
    assert_eq!(correct[1].value, CharState::Correct);
    assert_eq!(correct[2].value, CharState::Correct);
    assert_eq!(correct[3].value, CharState::Correct);
    assert_eq!(correct[4].value, CharState::Correct);
}

Next lets add a test for a word that doesn't match.

#[test]
fn test_check_word_correct_wrong() {
    let word = "apple";
    let correct = check_word_correct("brown", word);
    assert_eq!(correct[0].value, CharState::Wrong);
    assert_eq!(correct[1].value, CharState::Wrong);
    assert_eq!(correct[2].value, CharState::Wrong);
    assert_eq!(correct[3].value, CharState::Wrong);
    assert_eq!(correct[4].value, CharState::Wrong);
}

And last let's add a test with a mix of all three values.

#[test]
    fn test_check_word_correct_mixed() {
        let correct = check_word_correct("cheer", "close");
        assert_eq!(correct[0].value, CharState::Correct);
        assert_eq!(correct[1].value, CharState::Wrong);
        assert_eq!(correct[2].value, CharState::Wrong);
        assert_eq!(correct[3].value, CharState::Wrong);
        assert_eq!(correct[4].value, CharState::Exists);
    }

This last test also reveals an error in the implmentation if we reverse the words.

#[test]
    fn test_check_word_correct_mixed_dublicate() {
        let correct = check_word_correct("close", "cheer");
        assert_eq!(correct[0].value, CharState::Correct);
        assert_eq!(correct[1].value, CharState::Wrong);
        assert_eq!(correct[2].value, CharState::Exists);
        assert_eq!(correct[3].value, CharState::Exists);
        assert_eq!(correct[4].value, CharState::Wrong);
    }

Here only one "e" should have been marked as present, this is the power and limit of unit testing. They only cover the cases that you can think of so it's a continuous job to keep them up to date and add more tests when bugs are discovered.

For the next article in this series we will correct the Error in the check_word_correct function.