started ch11.3

This commit is contained in:
darkicewolf50 2025-02-11 17:35:32 -07:00
parent 1bed9a2f9b
commit d00c64b51b
2 changed files with 174 additions and 6 deletions

View File

@ -27,15 +27,16 @@
"state": {
"type": "markdown",
"state": {
"file": "Writing_Tests.md",
"file": "Test_Organization.md",
"mode": "source",
"source": false
},
"icon": "lucide-file",
"title": "Writing_Tests"
"title": "Test_Organization"
}
}
]
],
"currentTab": 1
}
],
"direction": "vertical"
@ -178,11 +179,12 @@
"command-palette:Open command palette": false
}
},
"active": "caf0233e624d6c1c",
"active": "53b36d00b704136e",
"lastOpenFiles": [
"Test Controls.md",
"Test_Organization.md",
"Tests.md",
"Writing_Tests.md",
"Test Controls.md",
"Traits.md",
"Modules and Use.md",
"Modules.md",
@ -205,7 +207,6 @@
"Project Organization.md",
"README.md",
"Reducing_Code_Duplication.md",
"String.md",
"does_not_compile.svg",
"Untitled.canvas",
"Good and Bad Code/Commenting Pratices",

167
Test_Organization.md Normal file
View File

@ -0,0 +1,167 @@
# Test Organization
The Rust community thinks about tests in terms of two main categories
- Unit tests
- Integration Tests
*Unit tests* are small and more focused, testing one module in isolation at a time and can test private interfaces
*Integration tests* are entirely external to your library and use your code in the same way any other external code would. Using only the public interface and potentially exercising multiple modules per test.
## Unit Tests
The purpose of these kinds of tests is to test each unit of code in isolation from the rest of the code to pinpoint where the code is and isn't working as expected
You put unit tests in the *src* directory in each file the code that they are testing.
The convention is to create a module named `tests` in each file to contain the test functions and to annotate the module with `cfg(test)`
### The Tests Module and `#[cfg(test)]`
The `#[cfg(test)]` annotation on the `tests` module tells Rust to compile and run the test code only when you run `cargo test`, not when you run `cargo build`.
This both saves time when compiling when you only want to build the library and saves space in the resultant compiled artifact because the tests are not included.
You will see that because integration tests go in a different directory, they don't need the `#[cfg(test)]` annotation.
However because unit tests go in the same files as the code you will use the `#[cfg(test)]` annotation to specify that they shouldn't be included in the compiled result
Recall when we generated the new `adder` project. Cargo generated this code for us:
```rust
pub fn add(left: usize, right: usize) -> usize {
left + right
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
let result = add(2, 2);
assert_eq!(result, 4);
}
}
```
On the auto generated `tests` module the attribute `cfg` stands for *configuration* and tells Rust that the following item should only be included given a certain config option
In this case the config option is `test` which is provided by Rust for compiling and running the tests.
Using the `cfg` attribute, Cargo compiles our test code only if we actively run the tests with `cargo test`
This includes any helper functions that might be within this module, and includes functions annotated with `#[test]`
### Testing Private Functions
It is debated about whether or not private functions should be tested directly and other languages make it difficult or impossible to do so.
Regardless of which testing ideology you believe, Rust's privacy rules do allow you to test private functions
Consider this code
```rust
pub fn add_two(a: usize) -> usize {
internal_adder(a, 2)
}
fn internal_adder(left: usize, right: usize) -> usize {
left + right
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn internal() {
let result = internal_adder(2, 2);
assert_eq!(result, 4);
}
}
```
Note that `internal_adder` function is not marked as `pub`.
Tests are just Rust code, and the `tests` module is just another module.
Because the module is a child module it can use the items in their ancestor modules.
In this test we bring all of the `tests` module's parent items into the scope using the `use super::*` and then we can call the private function `internal_adder`
If you don't believe that internal functions should be tested there is nothing in Rust that will compel you to do so.
## Integration Tests
In Rust integration tests are entirely external to library.
They use your library in the same way any any external code would, which means that they can only call functions that are part of your library's public API.
The purpose of these is to tests weather units work together correctly.
Units of code that work correctly on their own could have problems when put together, so test coverage of the integrated code is important as well.
To create integration tests you need a *tests* directory
### The *tests* Directory
We create a *tests* directory at the top level of our project directory next to *src*
Cargo knows how to look for integration test files in this directory
We can make as many as we want and Cargo will compile each of the files as an individual crate
Here is how it should be setup and what your file directory system should look like
```
adder
├── Cargo.lock
├── Cargo.toml
├── src
│   └── lib.rs
└── tests
└── integration_test.rs
```
Here is what tests/integration_test.rs should contain
```rust
use adder::add_two;
#[test]
fn it_adds_two() {
let result = add_two(2);
assert_eq!(result, 4);
}
```
Each file in the *tests* directory is a separate crate so we need to bring our library into each test crat's scope
So we need to use `use adder::add_two;` in this case. We don't need unit tests because we aren't writing tests for them.
We don't need to annotate any code in *tests/integration_test.rs* with `#[cfg(test)]`.
Cargo treats the *tests* directory specially and compiles files in this directly only when we run `cargo test`
Here would be the result output in this case
```
$ cargo test
Compiling adder v0.1.0 (file:///projects/adder)
Finished `test` profile [unoptimized + debuginfo] target(s) in 1.31s
Running unittests src/lib.rs (target/debug/deps/adder-1082c4b063a8fbe6)
running 1 test
test tests::internal ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Running tests/integration_test.rs (target/debug/deps/integration_test-1082c4b063a8fbe6)
running 1 test
test it_adds_two ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Doc-tests adder
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
```
There are three sections of the output
1. The Unit Tests
2. The Integration test
3. The Doc Tests
Note that if any test in a section fails the following sections will not run
For example if a unit test fails, there wont be any output for integration and doc tests because those tests will only be run if all unit tests are passing.