notes

Log | Files | Refs | README

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. ​