arrange_act_assert.md (6570B)
1 # Arrange-Act-Assert 2 3 ## The Pattern 4 5 Arrange-Act-Assert is a great way to structure test cases. It prescribes an 6 order of operations: 7 8 1. **Arrange** inputs and targets. Arrange steps should set up the test case. 9 Does the test require any objects or special settings? Does it need to prep a 10 database? Does it need to log into a web app? Handle all of these operations 11 at the start of the test. 12 13 2. **Act** on the target behavior. Act steps should cover the main thing to be 14 tested. This could be calling a function or method, calling a REST API, or 15 interacting with a web page. Keep actions focused on the target behavior. 16 17 3. **Assert** expected outcomes. Act steps should elicit some sort of response. 18 Assert steps verify the goodness or badness of that response. Sometimes, 19 assertions are as simple as checking numeric or string values. Other times, 20 they may require checking multiple facets of a system. Assertions will 21 ultimately determine if the test passes or fails. 22 23 **Behavior-Driven Development** follows the **Arrange-Act-Assert** pattern by 24 another name: **Given-When-Then**. The Gherkin language uses Given-When-Then 25 steps to specify behaviors in scenarios. Given-When-Then is essentially the same 26 formula as Arrange-Act-Assert. 27 28 ### References 29 30 [automationpanda](https://automationpanda.com/2020/07/07/arrange-act-assert-a-pattern-for-writing-good-tests/) 31 [semaphore](https://semaphore.io/blog/aaa-pattern-test-automation) 32 33 ## Example 34 35 Here is a simple Rust example demonstrating the Arrange-Act-Assert pattern using 36 a small `ShoppingCart` struct. Rust's built-in `#[cfg(test)]` module makes the 37 three phases very natural to express. 38 39 The code: 40 41 ```rust 42 // src/lib.rs 43 44 pub struct ShoppingCart { 45 items: Vec<(String, f64)>, // (name, price) 46 } 47 48 impl ShoppingCart { 49 pub fn new() -> Self { 50 ShoppingCart { items: vec![] } 51 } 52 53 pub fn add_item(&mut self, name: &str, price: f64) { 54 self.items.push((name.to_string(), price)); 55 } 56 57 pub fn total(&self) -> f64 { 58 self.items.iter().map(|(_, price)| price).sum() 59 } 60 61 pub fn item_count(&self) -> usize { 62 self.items.len() 63 } 64 } 65 ``` 66 67 The test: 68 69 ```rust 70 #[cfg(test)] 71 mod tests { 72 use super::*; 73 74 #[test] 75 fn test_total_reflects_added_items() { 76 // ── Arrange ────────────────────────────────────── 77 let mut cart = ShoppingCart::new(); 78 let expected_total = 17.97; 79 80 // ── Act ────────────────────────────────────────── 81 cart.add_item("Apple", 0.99); 82 cart.add_item("Bread", 2.49); 83 cart.add_item("Laptop", 14.49); 84 85 // ── Assert ─────────────────────────────────────── 86 assert!((cart.total() - expected_total).abs() < f64::EPSILON); 87 assert_eq!(cart.item_count(), 3); 88 } 89 90 #[test] 91 fn test_empty_cart_has_zero_total() { 92 // ── Arrange ────────────────────────────────────── 93 let cart = ShoppingCart::new(); 94 95 // ── Act ────────────────────────────────────────── 96 let total = cart.total(); 97 98 // ── Assert ─────────────────────────────────────── 99 assert_eq!(total, 0.0); 100 } 101 } 102 ``` 103 104 Flow Diagram: 105 106 ``` 107 ┌─────────────────────────────────────────────────────┐ 108 │ TEST FUNCTION │ 109 │ │ 110 │ ┌─────────────────────────────────────────────┐ │ 111 │ │ ARRANGE │ │ 112 │ │ • Create ShoppingCart::new() │ │ 113 │ │ • Define expected_total = 17.97 │ │ 114 │ └──────────────────┬──────────────────────────┘ │ 115 │ │ │ 116 │ ▼ │ 117 │ ┌─────────────────────────────────────────────┐ │ 118 │ │ ACT │ │ 119 │ │ • cart.add_item("Apple", 0.99) │ │ 120 │ │ • cart.add_item("Bread", 2.49) │ │ 121 │ │ • cart.add_item("Laptop", 14.49) │ │ 122 │ └──────────────────┬──────────────────────────┘ │ 123 │ │ │ 124 │ ▼ │ 125 │ ┌─────────────────────────────────────────────┐ │ 126 │ │ ASSERT │ │ 127 │ │ • cart.total() ≈ 17.97 ✔ / ✘ │ │ 128 │ │ • cart.item_count() == 3 ✔ / ✘ │ │ 129 │ └─────────────────────────────────────────────┘ │ 130 └─────────────────────────────────────────────────────┘ 131 ``` 132 133 Rust-specific notes: 134 135 - `#[cfg(test)]` gates the test module so it's only compiled during cargo test, 136 keeping production binaries lean. 137 138 - Floating-point assertions in Rust require a tolerance check 139 (`abs() < EPSILON`) rather than a direct `==`, since `f64` arithmetic can 140 introduce tiny rounding errors. 141 142 - Each test function is independent — no shared mutable state bleeds between 143 them, reinforcing the one behavior per test principle of AAA. 144 145 - The **Given-When-Then** equivalent here would be: Given an empty cart, When 146 three items are added, Then the total equals their sum.