Activity 28: Write Tests for Your Code
Goal
Practice writing unit tests for Rust code using the #[test] attribute and assert macros.
Setup Steps
1. Create a new Cargo project:
cargo new contact_tests
cd contact_tests
2. Create the module files:
Your project should have this structure:
contact_tests/
├── Cargo.toml
└── src/
├── main.rs (already exists - replace with code below)
├── person.rs (create this file)
├── storage.rs (create this file)
└── stats.rs (create this file)
3. Copy the code below into each file
Use the base code provided in the next section.
4. Verify it compiles:
cargo run
You should see output with counts, names, averages, etc.
Your Task
Add test modules to person.rs, storage.rs, and stats.rs (NOT main.rs).
What to test:
- Normal cases that should work
- Edge cases (empty inputs, boundary values, single items)
- Invalid inputs (use
#[should_panic]for functions that panic) - Custom error messages to explain test failures
How to run tests:
cargo test
Challenge yourself:
- Write 3-4 tests per module
- Add custom error messages to at least 2 tests
- Try breaking the code to see tests fail (then fix it!)
- Choose two tests you're proud of to submit on Gradescope with explanations
Base code
person.rs:
#![allow(unused)] fn main() { pub struct Person { // [H][a] - pub - used by main name: String, // private - only accessed through methods age: i32, // private score: f64, // private } fn validate_age(age: i32) -> bool { age > 0 && age < 150 } impl Person { pub fn new(name: String, age: i32, score: f64) -> Person { if validate_age(age) { Person { name, age, score } } else { panic!("Invalid age"); } } pub fn get_age(&self) -> i32 { self.age } pub fn get_name(&self) -> &str { &self.name } pub fn get_score(&self) -> f64 { self.score } } }
storage.rs
#![allow(unused)] fn main() { use crate::person::Person; // needs Person struct! pub fn add_person(people: &mut Vec<Person>, person: Person) { people.push(person); } pub fn count_people(people: &Vec<Person>) -> usize { people.len() } pub fn list_names(people: &Vec<Person>) -> Vec<String> { people.iter() .map(|p| p.get_name().to_string()) .collect() } pub fn format_person(p: &Person) -> String { format!("{} (age {}, score: {:.1})", p.get_name(), p.get_age(), p.get_score()) } }
stats.rs
#![allow(unused)] fn main() { use crate::person::Person; // needs Person struct! const MIN_PASSING_SCORE: f64 = 60.0; pub fn average_score(people: &Vec<Person>) -> f64 { let sum: f64 = people.iter() .map(|p| p.get_score()) .sum(); compute_average(sum, people.len()) } fn compute_average(sum: f64, count: usize) -> f64 { sum / count as f64 } pub fn highest_score(people: &Vec<Person>) -> f64 { people.iter() .map(|p| p.get_score()) .max_by(|a, b| a.partial_cmp(b).unwrap()) .unwrap() } pub fn count_passing(people: &Vec<Person>) -> usize { people.iter() .filter(|p| p.get_score() >= MIN_PASSING_SCORE) .count() } }
main.rs
mod person; mod storage; mod stats; use person::Person; use storage::{add_person, count_people, list_names}; use stats::{average_score, highest_score, count_passing}; fn main() { let mut people = Vec::new(); let alice = Person::new("Alice".to_string(), 25, 92.5); let bob = Person::new("Bob".to_string(), 30, 87.0); add_person(&mut people, alice); add_person(&mut people, bob); println!("Count: {}", count_people(&people)); println!("Names: {:?}", list_names(&people)); println!("Average: {:.1}", average_score(&people)); println!("Highest: {:.1}", highest_score(&people)); println!("Passing: {}", count_passing(&people)); }