TL;DR

  • dbg!๋กœ ๊ฐ’๊ณผ ๋ถ„๊ธฐ๋ฅผ ํ™•์ธํ•˜๊ณ  ์‹คํŒจํ•œ ์ž…๋ ฅ์„ ์ž‘์€ ํ…Œ์ŠคํŠธ๋กœ ์žฌํ˜„ํ•˜๋Š” ๊ฐœ๋ฐœ ํ๋ฆ„
  • cargo test์˜ targetยท์ด๋ฆ„ ํ•„ํ„ฐยท์ถœ๋ ฅ ์˜ต์…˜์„ ์‚ฌ์šฉํ•œ ๋ฐ˜๋ณต ๊ฒ€์ฆ
  • ์ˆ˜์ •ํ•œ ๋™์ž‘์€ ํšŒ๊ท€ ํ…Œ์ŠคํŠธ๋กœ ๋‚จ๊ธฐ๊ณ  ํ•„์š”์— ๋”ฐ๋ผ ๋ฌธ์„œ ํ…Œ์ŠคํŠธยท์ž๋™ ์‹คํ–‰ยทdebugger๋กœ ํ™•์žฅ

AI-assisted

1. ์˜ˆ์ œ ํ”„๋กœ์ ํŠธ ์ค€๋น„

์ƒํ’ˆ ๊ฐ€๊ฒฉ๊ณผ ์ˆ˜๋Ÿ‰์œผ๋กœ ์ตœ์ข… ์ฃผ๋ฌธ ๊ธˆ์•ก์„ ๊ณ„์‚ฐํ•˜๋Š” CLI๋ฅผ ๋งŒ๋“ ๋‹ค. ๊ณ„์‚ฐ ๊ธฐ๋Šฅ์€ library์— ๋‘๊ณ  CLI์—์„œ ํ˜ธ์ถœํ•œ๋‹ค.

  • ์ƒํ’ˆ ํ•ฉ๊ณ„: ์ƒํ’ˆ ๊ฐ€๊ฒฉ์— ์ˆ˜๋Ÿ‰์„ ๊ณฑํ•œ๋‹ค.
  • ๋ฐฐ์†ก๋น„: ์ƒํ’ˆ ํ•ฉ๊ณ„๊ฐ€ 5,000์› ๋ฏธ๋งŒ์ด๋ฉด 300์›, 5,000์› ์ด์ƒ์ด๋ฉด ๋ฌด๋ฃŒ๋‹ค.
  • ์ตœ์ข… ๊ธˆ์•ก: ์ƒํ’ˆ ํ•ฉ๊ณ„์™€ ๋ฐฐ์†ก๋น„๋ฅผ ๋”ํ•œ๋‹ค.

ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ

Rust์™€ Cargo๊ฐ€ ์„ค์น˜๋œ ํ™˜๊ฒฝ์—์„œ ์ƒˆ ํ”„๋กœ์ ํŠธ๋ฅผ ๋งŒ๋“ ๋‹ค. ์ดํ›„ ๋ช…๋ น์€ shop_debug ๋””๋ ‰ํ„ฐ๋ฆฌ์—์„œ ์‹คํ–‰ํ•œ๋‹ค.

cargo new shop_debug --lib --vcs none
cd shop_debug

์ดˆ๊ธฐ ์˜ˆ์ œ์˜ ํŒŒ์ผ ๊ตฌ์„ฑ์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค. ์ƒ์„ฑ๋œ src/lib.rs๋ฅผ ๋ฐ”๊พธ๊ณ  src/main.rs๋ฅผ ์ถ”๊ฐ€ํ•œ๋‹ค.

shop_debug/
โ”œโ”€โ”€ Cargo.toml
โ””โ”€โ”€ src/
    โ”œโ”€โ”€ lib.rs
    โ””โ”€โ”€ main.rs
  • src/lib.rs: ์ƒํ’ˆ ํ•ฉ๊ณ„์™€ ๋ฐฐ์†ก๋น„๋ฅผ ๊ณ„์‚ฐํ•˜๋Š” ํ•จ์ˆ˜.
  • src/main.rs: ๊ณ„์‚ฐ ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•˜๊ณ  ์ฃผ๋ฌธ ๊ธˆ์•ก์„ ์ถœ๋ ฅํ•˜๋Š” ์‹คํ–‰ ์ง„์ž…์ .

์ดˆ๊ธฐ ์ฝ”๋“œ

src/lib.rs๋ฅผ ๋‹ค์Œ ์ฝ”๋“œ๋กœ ๋ฐ”๊พผ๋‹ค. ๋ฐฐ์†ก๋น„ ์กฐ๊ฑด์— ๊ฒฝ๊ณ„๊ฐ’ ์˜ค๋ฅ˜๊ฐ€ ๋“ค์–ด ์žˆ๋‹ค.

pub fn total(unit_price: u32, quantity: u32) -> u32 {
    let subtotal = unit_price * quantity;
    subtotal + shipping_fee(subtotal)
}
 
fn shipping_fee(subtotal: u32) -> u32 {
    if subtotal <= 5000 { 300 } else { 0 }
}

src/main.rs๋ฅผ ์ถ”๊ฐ€ํ•œ๋‹ค.

fn main() {
    println!("์ฃผ๋ฌธ ๊ธˆ์•ก: {}", shop_debug::total(1000, 5));
}

์‹คํ–‰ ๊ฒฐ๊ณผ

1,000์›์งœ๋ฆฌ ์ƒํ’ˆ 5๊ฐœ๋ฅผ ์ฃผ๋ฌธํ•œ๋‹ค. ์ƒํ’ˆ ํ•ฉ๊ณ„๊ฐ€ 5,000์›์ด๋ฏ€๋กœ ๊ธฐ๋Œ€ํ•˜๋Š” ์ตœ์ข… ์ฃผ๋ฌธ ๊ธˆ์•ก๋„ 5,000์›์ด๋‹ค.

cargo run --quiet
์ฃผ๋ฌธ ๊ธˆ์•ก: 5300

์ปดํŒŒ์ผ์€ ์„ฑ๊ณตํ–ˆ์ง€๋งŒ ์‹ค์ œ ๊ธˆ์•ก์€ ๊ธฐ๋Œ€ํ•œ ๊ธˆ์•ก๋ณด๋‹ค 300์› ๋งŽ๋‹ค. ์ด ์ž…๋ ฅ์„ ์‚ฌ์šฉํ•ด ๊ณ„์‚ฐ ๊ณผ์ •์„ ์กฐ์‚ฌํ•˜๊ณ  ์ˆ˜์ •ํ•œ ๊ฒฐ๊ณผ๋ฅผ ํ…Œ์ŠคํŠธ๋กœ ๋‚จ๊ธด๋‹ค.


2. dbg๋กœ ๊ณ„์‚ฐ ๊ณผ์ • ํ™•์ธ

์ƒํ’ˆ ํ•ฉ๊ณ„์™€ ๋ฐฐ์†ก๋น„๋ฅผ ๋‚˜๋ˆ  ํ™•์ธํ•˜๋„๋ก src/lib.rs์˜ total()์„ ๋ฐ”๊พผ๋‹ค.

pub fn total(unit_price: u32, quantity: u32) -> u32 {
    let subtotal = dbg!(unit_price * quantity);
    let shipping = dbg!(shipping_fee(subtotal));
    subtotal + shipping
}

dbg!๋Š” ํ‘œํ˜„์‹๊ณผ ํ‰๊ฐ€ํ•œ ๊ฐ’, ์†Œ์Šค ์œ„์น˜๋ฅผ ์ถœ๋ ฅํ•˜๊ณ  ๊ทธ ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค. ์œ„ ์ฝ”๋“œ์—์„œ๋Š” ํ•ฉ๊ณ„๋ฅผ ๊ณ„์‚ฐํ•˜๋ฉด์„œ ์ถœ๋ ฅํ•˜๊ณ  ๋‹ค์Œ ๊ณ„์‚ฐ์—๋„ ์‚ฌ์šฉํ•œ๋‹ค.

cargo run --quiet

๋‹ค์Œ์€ stderr์˜ ์ง„๋‹จ ์ถœ๋ ฅ์ด๋‹ค. ํŒŒ์ผ์—์„œ ์ฝ”๋“œ์˜ ์œ„์น˜๋ฅผ ๋ฐ”๊พธ๋ฉด ์ค„ ๋ฒˆํ˜ธ๋„ ๋‹ฌ๋ผ์ง„๋‹ค.

[src/lib.rs:2:20] unit_price * quantity = 5000
[src/lib.rs:3:20] shipping_fee(subtotal) = 300
  • ์ƒํ’ˆ ํ•ฉ๊ณ„: unit_price * quantity๋Š” 5,000์œผ๋กœ ๋‚˜์™€ ๊ณฑ์…ˆ ๊ฒฐ๊ณผ๊ฐ€ ๋งž๋‹ค.
  • ๋ฐฐ์†ก๋น„: shipping_fee(subtotal)๋Š” 300์œผ๋กœ ๋‚˜์™€ ์กฐ์‚ฌํ•  ํ•จ์ˆ˜๊ฐ€ ์ขํ˜€์กŒ๋‹ค.
  • ์กฐ๊ฑด์‹: subtotal <= 5000์—๋Š” 5,000๋„ ํฌํ•จ๋œ๋‹ค. ๋ฌด๋ฃŒ ๋ฐฐ์†ก ์กฐ๊ฑด์— ๋งž์ถ”๋ ค๋ฉด < 5000์œผ๋กœ ๋ฐ”๊ฟ”์•ผ ํ•œ๋‹ค.

์กฐ๊ฑด ์ž์ฒด๋„ ๋ณด๊ณ  ์‹ถ๋‹ค๋ฉด ์ž ์‹œ ๋‹ค์Œ์ฒ˜๋Ÿผ ๊ฐ์‹ผ๋‹ค.

fn shipping_fee(subtotal: u32) -> u32 {
    if dbg!(subtotal <= 5000) { 300 } else { 0 }
}

์ถœ๋ ฅ ์„ ํƒ: println๊ณผ dbg

  • println!: ์ •ํ•œ ํ˜•์‹์œผ๋กœ stdout์— ์ถœ๋ ฅ. CLI์˜ ๊ฒฐ๊ณผ ํ‘œ์‹œ ๋“ฑ์— ์‚ฌ์šฉ.
  • dbg!: ์†Œ์Šค ์œ„์น˜ยทํ‘œํ˜„์‹ยท๊ฐ’์„ stderr์— ์ถœ๋ ฅ. ๊ณ„์‚ฐ ๊ฒฐ๊ณผ๋‚˜ ๋ถ„๊ธฐ๋ฅผ ์ž„์‹œ๋กœ ํ™•์ธํ•  ๋•Œ ์‚ฌ์šฉ.
  • ๋นŒ๋“œ ์ข…๋ฅ˜: dbg!๋Š” release ๋นŒ๋“œ์—์„œ๋„ ์‹คํ–‰. ์กฐ์‚ฌํ•œ ๋’ค ์ž„์‹œ ์ถœ๋ ฅ์„ ์ •๋ฆฌ.

๊ตฌ์กฐ์ฒด๋ฅผ ํ™•์ธํ•  ๋•Œ์˜ Debug์™€ ์†Œ์œ ๊ถŒ

์ฃผ๋ฌธ ์ •๋ณด๋ฅผ ๋ฌถ์–ด ํ™•์ธํ•˜๋ ค๋ฉด src/main.rs๋ฅผ ๋‹ค์Œ ์ฝ”๋“œ๋กœ ๋ฐ”๊ฟ” ์‹คํ–‰ํ•œ๋‹ค.

#[derive(Debug)]
struct Order {
    unit_price: u32,
    quantity: u32,
}
 
fn main() {
    let order = Order { unit_price: 1000, quantity: 5 };
 
    dbg!(&order);
    println!("์ฃผ๋ฌธ ๊ธˆ์•ก: {}", shop_debug::total(order.unit_price, order.quantity));
}
  • Debug ํŒŒ์ƒ: #[derive(Debug)]๋Š” ๊ตฌ์กฐ์ฒด์˜ ํ•„๋“œ๋ฅผ ๋””๋ฒ„๊น…์šฉ ํ˜•์‹์œผ๋กœ ์ถœ๋ ฅํ•  Debug ๊ตฌํ˜„์„ ์ƒ์„ฑํ•œ๋‹ค.
  • ๋นŒ๋ ค์„œ ์ถœ๋ ฅ: dbg!(&order)๋Š” ์ฃผ๋ฌธ์„ ๋นŒ๋ ค์„œ ์ถœ๋ ฅํ•œ๋‹ค. ์ถœ๋ ฅํ•œ ๋’ค์—๋„ order๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.
  • ๊ฐ’์œผ๋กœ ์ „๋‹ฌ: dbg!(order)์— ์ด ๊ตฌ์กฐ์ฒด๋ฅผ ๋„˜๊ธฐ๋ฉด Copy๋ฅผ ๊ตฌํ˜„ํ•˜์ง€ ์•Š์•˜์œผ๋ฏ€๋กœ ์†Œ์œ ๊ถŒ์„ ๋„˜๊ธด๋‹ค. ๋ฐ˜ํ™˜๊ฐ’์„ ๋ฐ›์ง€ ์•Š์œผ๋ฉด ์ดํ›„ ๊ฐ™์€ ๋ณ€์ˆ˜๋กœ ์ฃผ๋ฌธ์„ ์‚ฌ์šฉํ•˜์ง€ ๋ชปํ•œ๋‹ค.

์ผ๋ฐ˜ ์ถœ๋ ฅ์—์„œ๋„ Debug ํ˜•์‹์„ ์‚ฌ์šฉํ•œ๋‹ค.

println!("{:?}", order);   // ํ•œ ์ค„ ํ˜•์‹
println!("{:#?}", order);  // ์—ฌ๋Ÿฌ ์ค„๋กœ ํŽผ์นœ ํ˜•์‹

3. ์‹คํŒจํ•œ ์ฃผ๋ฌธ์˜ ํ…Œ์ŠคํŠธ ์žฌํ˜„

CLI๋กœ ์ž…๋ ฅ์„ ๋ฐ”๊พธ๋ฉฐ ๋งค๋ฒˆ ๋ˆˆ์œผ๋กœ ํ™•์ธํ•˜๋Š” ๋Œ€์‹  5,000์› ์ฃผ๋ฌธ์˜ ๊ธฐ๋Œ€ ๊ฒฐ๊ณผ๋ฅผ ์ฝ”๋“œ๋กœ ๋‚จ๊ธด๋‹ค. src/lib.rs์˜ ์•„๋ž˜์— ๋‹ค์Œ module์„ ์ถ”๊ฐ€ํ•œ๋‹ค.

#[cfg(test)]
mod tests {
    use super::*;
 
    #[test]
    fn free_shipping_at_threshold() {
        let actual = total(1000, 5);
        assert_eq!(actual, 5000, "5,000์› ์ฃผ๋ฌธ์€ ๋ฌด๋ฃŒ ๋ฐฐ์†ก์ด์–ด์•ผ ํ•œ๋‹ค");
    }
}

ํ…Œ์ŠคํŠธ ํ•จ์ˆ˜๋Š” ์ž…๋ ฅ์„ ์ค€๋น„ํ•˜๊ณ  ์ฝ”๋“œ๋ฅผ ํ˜ธ์ถœํ•œ ๋’ค ๊ธฐ๋Œ€ ๊ฒฐ๊ณผ๋ฅผ ๊ฒ€์‚ฌํ•œ๋‹ค. ์—ฌ๊ธฐ์„œ๋Š” ์‹ค์ œ ์ฃผ๋ฌธ ๊ธˆ์•ก๊ณผ 5,000์„ ๋น„๊ตํ•œ๋‹ค.

๊ตฌ๋ฌธ์—ญํ• 
#[cfg(test)]ํ…Œ์ŠคํŠธ ๋ชจ๋“œ๋กœ ์ปดํŒŒ์ผํ•  ๋•Œ ์ด module์„ ํฌํ•จ
mod testsํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ๋ฌถ๋Š” ์ผ๋ฐ˜ module. tests๋Š” ๊ด€๋ก€์ ์ธ ์ด๋ฆ„
use super::*;๋ถ€๋ชจ module์˜ ํ•ญ๋ชฉ์„ ํ˜„์žฌ ๋ฒ”์œ„๋กœ ๊ฐ€์ ธ์˜ด
#[test]ํ…Œ์ŠคํŠธ ์‹คํ–‰๊ธฐ๊ฐ€ ์‹คํ–‰ํ•  ํ•จ์ˆ˜ ํ‘œ์‹œ
assert_eq!๋‘ ๊ฐ’์ด ๊ฐ™์€์ง€ ๊ฒ€์‚ฌํ•˜๊ณ  ๋‹ค๋ฅด๋ฉด panic์œผ๋กœ ์‹คํŒจ ์ฒ˜๋ฆฌ
cargo test --lib free_shipping_at_threshold

์‹คํŒจ ์ถœ๋ ฅ์—์„œ ๋‘ ๊ฐ’์„ ๋น„๊ตํ•œ๋‹ค.

left: 5300
right: 5000

์ด๋•Œ ํ•จ์ˆ˜ ์•ˆ์— ๋‘” dbg!์˜ ์ถœ๋ ฅ๋„ ์‹คํŒจ ๋ณด๊ณ ์— ํฌํ•จ๋œ๋‹ค. ๊ธฐ๋Œ€ ๊ฒฐ๊ณผ๋Š” ์œ ์ง€ํ•œ ์ฑ„ ํ•ฉ๊ณ„์™€ ๋ฐฐ์†ก๋น„๋ฅผ ํ™•์ธํ•˜๊ณ  ์›์ธ์„ ๊ณ ์นœ๋‹ค.

shipping_fee()๋ฅผ ๋‹ค์Œ์œผ๋กœ ๋ฐ”๊พผ ๋’ค ๊ฐ™์€ ๋ช…๋ น์„ ๋‹ค์‹œ ์‹คํ–‰ํ•œ๋‹ค.

fn shipping_fee(subtotal: u32) -> u32 {
    if subtotal < 5000 { 300 } else { 0 }
}

ํ…Œ์ŠคํŠธ๊ฐ€ ํ†ต๊ณผํ•˜๋ฉด ๋ฐฐ์†ก๋น„ ๊ธฐ์ค€ ์•„๋ž˜ยท์œ„์˜ ์ฃผ๋ฌธ๋„ mod tests ์•ˆ์— ์ถ”๊ฐ€ํ•œ๋‹ค.

#[test]
fn shipping_below_threshold() {
    assert_eq!(total(1000, 3), 3300);
}
 
#[test]
fn free_shipping_above_threshold() {
    assert_eq!(total(1000, 6), 6000);
}
cargo test --lib

์›์ธ์„ ํ™•์ธํ•œ dbg!๋Š” ์ง€์šฐ๊ณ  total()์„ ๋‹ค์‹œ ๋‹จ์ˆœํ•œ ๊ณ„์‚ฐ์œผ๋กœ ๋ฐ”๊พผ๋‹ค. ์„ธ ํ…Œ์ŠคํŠธ๋Š” ๋ฐฐ์†ก๋น„ ๊ทœ์น™์ด ์ดํ›„ ๋ณ€๊ฒฝ์—์„œ๋„ ์œ ์ง€๋˜๋Š”์ง€ ํ™•์ธํ•˜๋Š” ํšŒ๊ท€ ํ…Œ์ŠคํŠธ๋กœ ๋‚จ๊ธด๋‹ค.

Assertion ์„ ํƒ: ๊ฒ€์ฆํ•  ์กฐ๊ฑด์— ๋งž์ถค

  • assert!: ์กฐ๊ฑด์ด true์ธ์ง€ ๊ฒ€์‚ฌ. ์˜ค๋ฅ˜ ์—ฌ๋ถ€๋‚˜ ๋ฒ”์œ„ ๊ฒ€์‚ฌ ๋“ฑ์— ์‚ฌ์šฉ.
  • assert_eq!: ๋‘ ๊ฐ’์˜ ๊ฐ™์Œ์„ ๊ฒ€์‚ฌ. ์‹คํŒจํ•˜๋ฉด ๋น„๊ตํ•œ ๊ฐ’๋„ ํ‘œ์‹œ.
  • assert_ne!: ๋‘ ๊ฐ’์ด ๋‹ค๋ฅธ์ง€ ๊ฒ€์‚ฌ.
  • ์ถ”๊ฐ€ ๋ฉ”์‹œ์ง€: assertion ๋’ค์— ์‹คํŒจ ์ƒํ™ฉ์„ ์„ค๋ช…ํ•˜๋Š” ํ˜•์‹ ๋ฌธ์ž์—ด ์ง€์ • ๊ฐ€๋Šฅ.

pytest ๋น„๊ต: ๊ฒ€์ฆ ์ฝ”๋“œ์™€ ํ…Œ์ŠคํŠธ ํƒ์ƒ‰

  • ๊ฒ€์ฆ ์ฝ”๋“œ: pytest์˜ assert total(1000, 5) == 5000๊ณผ Rust์˜ assert_eq!(total(1000, 5), 5000)์€ ๊ธฐ๋Œ€๊ฐ’์„ ๊ฒ€์ฆํ•˜๋Š” ์—ญํ• ์ด ๊ฐ™์Œ.
  • ํ…Œ์ŠคํŠธ ํƒ์ƒ‰: pytest๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ํŒŒ์ผยทํ•จ์ˆ˜ ์ด๋ฆ„ ๊ทœ์น™์œผ๋กœ ์ˆ˜์ง‘. Rust๋Š” #[test]๋กœ ํ•จ์ˆ˜๋ฅผ ํ‘œ์‹œํ•˜๋ฏ€๋กœ ํ•จ์ˆ˜ ์ด๋ฆ„์˜ test_ ์ ‘๋‘์‚ฌ๋Š” ํ•„์ˆ˜๊ฐ€ ์•„๋‹˜.
  • ์‹คํŒจ ๋ณด๊ณ : pytest๋Š” assert์˜ ๋น„๊ต๊ฐ’์„ ํ’€์–ด ํ‘œ์‹œ. Rust์˜ assert_eq!๋„ ์‹คํŒจ ์‹œ ๋‘ ๊ฐ’์„ ํ‘œ์‹œ.

4. ๊ฐ€๊ฒฉ ์ž…๋ ฅ ์˜ค๋ฅ˜์™€ ์ˆ˜๋Ÿ‰ ๊ณ„์•ฝ ๊ฒ€์ฆ

CLI์—์„œ ๋ฐ›์€ ๊ฐ€๊ฒฉ ๋ฌธ์ž์—ด์„ ์ˆซ์ž๋กœ ๋ฐ”๊พธ๋Š” ๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ํ•œ๋‹ค. ์ •์ƒ ์ž…๋ ฅ๋ฟ ์•„๋‹ˆ๋ผ ์ˆซ์ž๊ฐ€ ์•„๋‹Œ ์ž…๋ ฅ๋„ ํ™•์ธํ•œ๋‹ค.

src/lib.rs์˜ ์ผ๋ฐ˜ ํ•จ์ˆ˜ ๋ถ€๋ถ„์„ ๋‹ค์Œ์œผ๋กœ ๋ฐ”๊พผ๋‹ค. ์•„๋ž˜์˜ ๊ธฐ์กด mod tests๋Š” ์œ ์ง€ํ•œ๋‹ค.

use std::num::ParseIntError;
 
pub fn parse_price(input: &str) -> Result<u32, ParseIntError> {
    input.trim().parse()
}
 
pub fn total(unit_price: u32, quantity: u32) -> u32 {
    assert!(quantity > 0, "quantity must be positive");
 
    let subtotal = unit_price * quantity;
    subtotal + shipping_fee(subtotal)
}
 
fn shipping_fee(subtotal: u32) -> u32 {
    if subtotal < 5000 { 300 } else { 0 }
}
  • ๊ฐ€๊ฒฉ ์ž…๋ ฅ: ์ˆซ์ž ๋ณ€ํ™˜์— ์‹คํŒจํ•˜๋ฉด Err๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค. ํ˜ธ์ถœ์ž๋Š” ์ž…๋ ฅ ์˜ค๋ฅ˜๋ฅผ ์ฒ˜๋ฆฌํ•œ๋‹ค.
  • ์ˆ˜๋Ÿ‰ ๊ณ„์•ฝ: ์ด ๊ณ„์‚ฐ API๋Š” ์ฃผ๋ฌธ ํ•ญ๋ชฉ์˜ ์ˆ˜๋Ÿ‰์ด ์–‘์ˆ˜๋ผ๊ณ  ์ •ํ–ˆ๋‹ค. ํ˜ธ์ถœ์ž๊ฐ€ ๊ณ„์•ฝ์„ ์–ด๊ธฐ๋ฉด assertion์œผ๋กœ panicํ•œ๋‹ค.

CLI๋„ ์ž…๋ ฅ ๋ฌธ์ž์—ด์„ ์‚ฌ์šฉํ•˜๋„๋ก src/main.rs๋ฅผ ๋ฐ”๊พผ๋‹ค. ์ฒซ ๋ฒˆ์งธ ์ธ์ˆ˜๊ฐ€ ์—†์œผ๋ฉด ๊ฐ€๊ฒฉ์„ 1000์œผ๋กœ ์ •ํ•˜๊ณ  ํŒŒ์‹ฑ ์˜ค๋ฅ˜๋Š” ?๋กœ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let input = std::env::args().nth(1).unwrap_or_else(|| "1000".to_string());
    let price = shop_debug::parse_price(&input)?;
 
    println!("์ฃผ๋ฌธ ๊ธˆ์•ก: {}", shop_debug::total(price, 5));
    Ok(())
}
cargo run --quiet -- ' 1000 '
cargo run --quiet -- free

์ฒซ ๋ช…๋ น์€ ์ฃผ๋ฌธ ๊ธˆ์•ก 5,000์„ ์ถœ๋ ฅํ•˜๊ณ  ๋‘ ๋ฒˆ์งธ ๋ช…๋ น์€ ๊ฐ€๊ฒฉ ๋ณ€ํ™˜ ์˜ค๋ฅ˜๋กœ ์ข…๋ฃŒํ•œ๋‹ค. ์ด ์ž…๋ ฅ์„ ๊ฐ๊ฐ ํ…Œ์ŠคํŠธ์— ์˜ฎ๊ธด๋‹ค.

์ •์ƒ ๊ฒฐ๊ณผ์™€ Err ํ™•์ธ

๋‹ค์Œ ํ…Œ์ŠคํŠธ๋ฅผ ๊ธฐ์กด mod tests ์•ˆ์— ์ถ”๊ฐ€ํ•œ๋‹ค.

#[test]
fn parse_price_trims_spaces() {
    assert_eq!(parse_price(" 1000 ").unwrap(), 1000);
}
 
#[test]
fn parse_price_rejects_text() {
    assert!(parse_price("free").is_err());
}

์ •์ƒ ์ž…๋ ฅ ํ…Œ์ŠคํŠธ์˜ unwrap()์€ ์‹คํŒจํ•˜๋ฉด ํ…Œ์ŠคํŠธ๋ฅผ panic์œผ๋กœ ๋๋‚ธ๋‹ค. ์˜ค๋ฅ˜ ์ž…๋ ฅ ํ…Œ์ŠคํŠธ์—์„œ๋Š” Err๊ฐ€ ๋‚˜์˜ค๋Š” ๋™์ž‘์„ ๊ธฐ๋Œ€ํ•˜๋ฏ€๋กœ is_err()๋กœ ๊ฒ€์‚ฌํ•œ๋‹ค.

์—ฌ๋Ÿฌ ํ•จ์ˆ˜๊ฐ€ Result๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค๋ฉด ํ…Œ์ŠคํŠธ ํ•จ์ˆ˜๋„ Result๋ฅผ ๋ฐ˜ํ™˜ํ•˜๊ณ  ?๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.

#[test]
fn parsed_price_calculates_total() -> Result<(), Box<dyn std::error::Error>> {
    let price = parse_price("1000")?;
    assert_eq!(total(price, 5), 5000);
    Ok(())
}
  • ์„ฑ๊ณต ๋ฐ˜ํ™˜: Ok(())๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒฝ๋กœ๋‹ค. ์•ž์˜ assertion๋„ ํ†ต๊ณผํ•ด์•ผ ํ•œ๋‹ค.
  • Err ๋ฐ˜ํ™˜: ?๊ฐ€ ์˜ค๋ฅ˜๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋ฉด ํ…Œ์ŠคํŠธ๊ฐ€ ์‹คํŒจํ•œ๋‹ค.
  • Box<dyn std::error::Error>: ์—ฌ๋Ÿฌ ์ข…๋ฅ˜์˜ ์˜ค๋ฅ˜๋ฅผ ๋ฐ˜ํ™˜ํ•  ๋•Œ ์“ฐ๋Š” ํ˜•ํƒœ๋‹ค. ์ด ์˜ˆ์ œ์ฒ˜๋Ÿผ ํŒŒ์‹ฑ ์˜ค๋ฅ˜๋งŒ ์žˆ์œผ๋ฉด ๋ฐ˜ํ™˜ ํƒ€์ž…์„ Result<(), ParseIntError>๋กœ ์ขํ˜€๋„ ๋œ๋‹ค.

์˜ˆ์ƒํ•œ panic ํ™•์ธ

์ˆ˜๋Ÿ‰ ๊ณ„์•ฝ๋„ ํ…Œ์ŠคํŠธ๋กœ ๋‚จ๊ธด๋‹ค.

#[test]
#[should_panic(expected = "quantity must be positive")]
fn zero_quantity_panics() {
    total(1000, 0);
}

#[should_panic]์€ ํ•จ์ˆ˜๊ฐ€ panicํ•ด์•ผ ํ†ต๊ณผํ•œ๋‹ค. expected๋ฅผ ์ง€์ •ํ•˜๋ฉด panic ๋ฉ”์‹œ์ง€์— ์ด ๋ฌธ์ž์—ด์ด ํฌํ•จ๋˜์–ด์•ผ ํ•˜๋ฏ€๋กœ ๋‹ค๋ฅธ ์›์ธ์˜ panic์œผ๋กœ ํ†ต๊ณผํ•˜๋Š” ๋ฒ”์œ„๋ฅผ ์ค„์ธ๋‹ค.

cargo test --lib

์ด ๋‹จ๊ณ„์—์„œ๋Š” ๋ฐฐ์†ก๋น„ ํ…Œ์ŠคํŠธ ์„ธ ๊ฐœ์™€ ํŒŒ์‹ฑยท์ˆ˜๋Ÿ‰ ํ…Œ์ŠคํŠธ ๋„ค ๊ฐœ๋ฅผ ํ•ฉ์ณ ์ผ๊ณฑ ๊ฐœ๊ฐ€ ํ†ต๊ณผํ•œ๋‹ค.

ํ…Œ์ŠคํŠธ ๋ฐ˜ํ™˜๊ฐ’: Result์™€ should_panic

  • Result ํ…Œ์ŠคํŠธ: Ok(())๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋ฉด ํ†ต๊ณผํ•˜๊ณ  Err๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋ฉด ์‹คํŒจ.
  • #[should_panic] ํ…Œ์ŠคํŠธ: ๋ฐ˜ํ™˜ ํƒ€์ž…์ด ()์ธ ํ•จ์ˆ˜์— ์ ์šฉ. Result ๋ฐ˜ํ™˜ ํ…Œ์ŠคํŠธ์™€ ๋”ฐ๋กœ ์ž‘์„ฑ.
  • ๋ช…์„ธ: ๋ฐ˜ํ™˜๊ฐ’๊ณผ panic ํŒ์ •์˜ ์„ธ๋ถ€ ๊ทœ์น™์€ ํ…Œ์ŠคํŠธ attribute ์ฐธ๊ณ .

pytest ๋น„๊ต: ์˜ˆ์ƒ ์˜ˆ์™ธ์™€ panic ๊ฒ€์ฆ

  • ์˜ˆ์ƒ ์‹คํŒจ: pytest๋Š” pytest.raises()๋กœ ์ง€์ •ํ•œ ์˜ˆ์™ธ ๋ฐœ์ƒ์„ ๊ฒ€์ฆ. Rust๋Š” #[should_panic]์œผ๋กœ ํ…Œ์ŠคํŠธ ํ•จ์ˆ˜์˜ panic์„ ๊ฒ€์ฆ.
  • ๋ฉ”์‹œ์ง€ ์กฐ๊ฑด: pytest.raises(..., match=...)๋Š” ์ •๊ทœ์‹์œผ๋กœ ๋น„๊ต. #[should_panic(expected = "...")]๋Š” ๋ฌธ์ž์—ด ํฌํ•จ ์—ฌ๋ถ€๋กœ ๋น„๊ต.
  • ์˜ค๋ฅ˜ ๋ฐ˜ํ™˜: Rust์˜ Result::Err๋Š” ๋ฐ˜ํ™˜๊ฐ’์ด๋ฏ€๋กœ assertion์œผ๋กœ ํ™•์ธ. ์ด ์˜ˆ์ œ์˜ parse_price_rejects_text()๋Š” is_err()๋กœ ๊ฒ€์‚ฌ.

5. ํ…Œ์ŠคํŠธ ์„ ํƒ ์‹คํ–‰๊ณผ ์ถœ๋ ฅ ํ™•์ธ

๊ฐœ๋ฐœ ์ค‘์—๋Š” ์ˆ˜์ • ์ค‘์ธ ํ•จ์ˆ˜์˜ ํ…Œ์ŠคํŠธ๋ฅผ ๋จผ์ € ์‹คํ–‰ํ•œ๋‹ค. ํ…Œ์ŠคํŠธ ์‹คํ–‰ ์˜ต์…˜์œผ๋กœ ์ด๋ฆ„๊ณผ ์‹คํ–‰ ๋ฒ”์œ„๋ฅผ ์ขํžŒ๋‹ค.

Target๊ณผ ์ด๋ฆ„ ํ•„ํ„ฐ

# package์˜ ๊ธฐ๋ณธ ํ…Œ์ŠคํŠธ ๋Œ€์ƒ ์‹คํ–‰
cargo test
 
# library์˜ unit test์—์„œ ์ด๋ฆ„์— parse_price๊ฐ€ ํฌํ•จ๋œ ํ…Œ์ŠคํŠธ ์‹คํ–‰
cargo test --lib parse_price
 
# library์˜ unit test ํ•˜๋‚˜๋ฅผ ์ „์ฒด ์ด๋ฆ„์œผ๋กœ ์ง€์ •
cargo test --lib tests::free_shipping_at_threshold -- --exact
 
# ํ…Œ์ŠคํŠธ ์ด๋ฆ„๊ณผ ๋ชฉ๋ก ํ™•์ธ
cargo test --lib -- --list
  • ๋ถ€๋ถ„ ์ด๋ฆ„: ๊ธฐ๋ณธ ์ด๋ฆ„ ํ•„ํ„ฐ๋Š” ๋ถ€๋ถ„ ๋ฌธ์ž์—ด์„ ๋น„๊ตํ•œ๋‹ค. parse_price๋Š” ๋‘ ํŒŒ์‹ฑ ํ…Œ์ŠคํŠธ์— ํ•ด๋‹นํ•œ๋‹ค.
  • ์ „์ฒด ์ด๋ฆ„: --exact์—์„œ๋Š” module ๊ฒฝ๋กœ๊นŒ์ง€ ๋น„๊ตํ•œ๋‹ค. ์—ฌ๊ธฐ์„œ๋Š” tests::free_shipping_at_threshold๊ฐ€ ์ „์ฒด ์ด๋ฆ„์ด๋‹ค.
  • Target: --lib๋Š” library์˜ unit test๋ฅผ ์„ ํƒํ•œ๋‹ค. binary์˜ unit test๋Š” ๋ณ„๋„ ๋Œ€์ƒ์ด๋‹ค.
์ธ์ˆ˜ ์œ„์น˜์ฒ˜๋ฆฌํ•˜๋Š” ๋Œ€์ƒ์˜ˆ
-- ์•ž์˜ Cargo ์˜ต์…˜๋นŒ๋“œํ•  packageยทtarget ๋“ฑ์„ ์„ ํƒ--lib, --release
-- ์•ž์˜ ์ด๋ฆ„ ํ•„ํ„ฐCargo๊ฐ€ ํ…Œ์ŠคํŠธ ์‹คํ–‰๊ธฐ๋กœ ์ „๋‹ฌparse_price
-- ๋’ค์˜ ์˜ต์…˜ํ…Œ์ŠคํŠธ ์‹คํ–‰๊ธฐ๊ฐ€ ์‹คํ–‰ ๋ฐฉ๋ฒ•์„ ์กฐ์ ˆ--exact, --nocapture

ํ†ต๊ณผํ•œ ํ…Œ์ŠคํŠธ์˜ ์ถœ๋ ฅ ํ™•์ธ

ํ…Œ์ŠคํŠธ ์•ˆ์—์„œ ๊ฐ’์ด ๊ถ๊ธˆํ•˜๋ฉด ์ž ์‹œ ๋‹ค์Œ์ฒ˜๋Ÿผ ๋ฐ”๊พผ๋‹ค.

#[test]
fn free_shipping_at_threshold() {
    let actual = dbg!(total(1000, 5));
    assert_eq!(actual, 5000);
}
# ์ถœ๋ ฅ ์บก์ฒ˜๋ฅผ ๋„๊ณ  ์‹คํ–‰ ์ค‘ ์ถœ๋ ฅ ํ™•์ธ
cargo test --lib free_shipping_at_threshold -- --nocapture
 
# ์บก์ฒ˜๋Š” ์œ ์ง€ํ•˜๊ณ  ํ†ต๊ณผํ•œ ํ…Œ์ŠคํŠธ์˜ ์ถœ๋ ฅ๋„ ์‹คํ–‰ ํ›„ ํ‘œ์‹œ
cargo test --lib free_shipping_at_threshold -- --show-output
  • ๊ธฐ๋ณธ ์‹คํ–‰: ์„ฑ๊ณตํ•œ ํ…Œ์ŠคํŠธ์˜ ์ถœ๋ ฅ์€ ์ˆจ๊ธฐ๊ณ  ์‹คํŒจํ•œ ํ…Œ์ŠคํŠธ๋Š” ์บก์ฒ˜ํ•œ ์ถœ๋ ฅ๋„ ๋ณด๊ณ ํ•œ๋‹ค.
  • --nocapture: ์ถœ๋ ฅ์„ ๋ฐ”๋กœ ๋‚ด๋ณด๋‚ธ๋‹ค. ์—ฌ๋Ÿฌ ํ…Œ์ŠคํŠธ๊ฐ€ ๋ณ‘๋ ฌ๋กœ ์‹คํ–‰๋˜๋ฉด ์ถœ๋ ฅ์ด ์„ž์ผ ์ˆ˜ ์žˆ๋‹ค.
  • --show-output: ์บก์ฒ˜ํ•œ ์„ฑ๊ณต ์ถœ๋ ฅ๋„ ๊ฒฐ๊ณผ์— ํฌํ•จํ•œ๋‹ค. ํ…Œ์ŠคํŠธ๋ณ„ ์ถœ๋ ฅ์„ ๋ชจ์•„์„œ ์ฝ์„ ๋•Œ ์‚ฌ์šฉํ•œ๋‹ค.

์˜ค๋ž˜ ๊ฑธ๋ฆฌ๋Š” ํ…Œ์ŠคํŠธ์™€ ๊ณต์œ  ์ƒํƒœ

์™ธ๋ถ€ ์„œ๋น„์Šค๋‚˜ ๋งŽ์€ ๋ฐ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ํ…Œ์ŠคํŠธ๋Š” #[ignore = "์ด์œ "]๋กœ ๊ธฐ๋ณธ ์‹คํ–‰์—์„œ ์ œ์™ธํ•  ์ˆ˜ ์žˆ๋‹ค. Ignore๋œ ํ…Œ์ŠคํŠธ๋„ ์ปดํŒŒ์ผ์€ ํ•œ๋‹ค.

# ignore ํ‘œ์‹œ๊ฐ€ ๋ถ™์€ ํ…Œ์ŠคํŠธ๋งŒ ์‹คํ–‰
cargo test -- --ignored
 
# ignore๋œ ํ…Œ์ŠคํŠธ๋„ ํฌํ•จํ•ด ์‹คํ–‰
cargo test -- --include-ignored
 
# ์ด๋ฆ„์— slow๊ฐ€ ํฌํ•จ๋œ ํ…Œ์ŠคํŠธ ์ œ์™ธ
cargo test -- --skip slow
 
# ํ…Œ์ŠคํŠธ ํ•จ์ˆ˜๋ฅผ ํ•œ ๋ฒˆ์— ํ•˜๋‚˜์”ฉ ์‹คํ–‰
cargo test -- --test-threads=1

๊ณต์œ  ์ƒํƒœ: ํ…Œ์ŠคํŠธ์˜ ์‹คํ–‰ ์ˆœ์„œ์— ์˜์กดํ•˜์ง€ ์•Š๊ธฐ

  • ๊ธฐ๋ณธ ์‹คํ–‰: ๊ฐ™์€ ํ…Œ์ŠคํŠธ ์‹คํ–‰ ํŒŒ์ผ์˜ ํ•จ์ˆ˜๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ๋ณ‘๋ ฌ ์‹คํ–‰.
  • ์ถฉ๋Œ ์‚ฌ๋ก€: ๊ฐ™์€ ํŒŒ์ผ์„ ๋ฎ์–ด์“ฐ๊ฑฐ๋‚˜ ํ™˜๊ฒฝ ๋ณ€์ˆ˜ยท์ „์—ญ ์ƒํƒœ๋ฅผ ๊ณต์œ ํ•˜๋ฉด ์„œ๋กœ ์˜ํ–ฅ์„ ์ค„ ์ˆ˜ ์žˆ์Œ.
  • ์กฐ์‚ฌ ๋ฐฉ๋ฒ•: ์ˆœ์ฐจ ์‹คํ–‰์œผ๋กœ ์ฆ์ƒ ๋ณ€ํ™”๋ฅผ ํ™•์ธํ•œ ๋’ค ํ…Œ์ŠคํŠธ๋ณ„ ๋ฐ์ดํ„ฐ๋‚˜ ์ž์›์„ ๋ถ„๋ฆฌ.
  • ์ˆœ์„œ: ์ˆœ์ฐจ ์‹คํ–‰ ์˜ต์…˜๋„ ํŠน์ • ํ…Œ์ŠคํŠธ๊ฐ€ ๋จผ์ € ์‹คํ–‰๋œ๋‹ค๋Š” ์˜์กด ๊ด€๊ณ„๋ฅผ ๋ณด์žฅํ•˜๋Š” ์ˆ˜๋‹จ์€ ์•„๋‹˜.

pytest ๋น„๊ต: ํ…Œ์ŠคํŠธ ์„ ํƒ๊ณผ ์ถœ๋ ฅ ์บก์ฒ˜

  • ์ด๋ฆ„ ์„ ํƒ: pytest์˜ -k์™€ Rust์˜ ์ด๋ฆ„ ํ•„ํ„ฐ๋Š” ๊ด€์‹ฌ ์žˆ๋Š” ํ…Œ์ŠคํŠธ๋งŒ ๊ณจ๋ผ ์‹คํ–‰. -k๋Š” andยทorยทnot์„ ์กฐํ•ฉํ•˜๋ฉฐ Rust์˜ ๊ธฐ๋ณธ ์ด๋ฆ„ ํ•„ํ„ฐ๋Š” ๋ถ€๋ถ„ ๋ฌธ์ž์—ด์„ ๋น„๊ต.
  • ์ถœ๋ ฅ ๊ด€์ฐฐ: pytest์˜ -s์™€ Rust์˜ --nocapture๋Š” ์บก์ฒ˜๋ฅผ ๊บผ ์‹คํ–‰ ์ค‘ ์ถœ๋ ฅ์„ ํ™•์ธํ•˜๋Š” ์˜ต์…˜.
  • ์ „์ฒด ์ด๋ฆ„: pytest๋Š” ํŒŒ์ผ ๊ฒฝ๋กœ์™€ ::ํ•จ์ˆ˜๋ช…์œผ๋กœ ์‹คํ–‰ํ•  ํ…Œ์ŠคํŠธ๋ฅผ ์ง€์ •. Rust ์˜ˆ์ œ๋Š” tests::free_shipping_at_threshold ๊ฐ™์€ ์ „์ฒด ์ด๋ฆ„๊ณผ --exact๋ฅผ ์‚ฌ์šฉ.

6. ๊ณต๊ฐœ API์™€ ๋ฌธ์„œ ์˜ˆ์ œ ๊ฒ€์ฆ

์ง€๊ธˆ๊นŒ์ง€ ์ž‘์„ฑํ•œ ์ฝ”๋“œ๋Š” unit test๋‹ค. ๊ฐ€๊ฒฉ ๊ณ„์‚ฐ ํ•จ์ˆ˜๋ฅผ library ๋ฐ–์—์„œ ์‚ฌ์šฉํ•˜๋Š” ๋ชจ์Šต๊ณผ ๋ฌธ์„œ์— ์ ์€ ์‚ฌ์šฉ๋ฒ•๋„ ๊ฒ€์‚ฌํ•œ๋‹ค.

์ข…๋ฅ˜๊ธฐ๋ณธ ์œ„์น˜๊ฒ€์ฆํ•  ๋‚ด์šฉ
Unit test์†Œ์Šค ๋‚ด๋ถ€์˜ #[cfg(test)] mod tests์ž‘์€ ํ•จ์ˆ˜์™€ ๋‚ด๋ถ€ ๊ตฌํ˜„
Integration testtests/*.rs์™ธ๋ถ€ crate ๊ด€์ ์˜ ๊ณต๊ฐœ API
Doc test/// ๋ฌธ์„œ ์ฃผ์„ ์•ˆ์˜ Rust ์˜ˆ์ œAPI ๋ฌธ์„œ์˜ ์‚ฌ์šฉ ์ฝ”๋“œ

๊ฐ™์€ module์˜ ๋‚ด๋ถ€ ๊ตฌํ˜„ ๊ฒ€์‚ฌ

private ํ•จ์ˆ˜๊ฐ€ ๋ณต์žกํ•ด์กŒ๋‹ค๋ฉด mod tests์— ๋‹ค์Œ ํ…Œ์ŠคํŠธ๋„ ์ถ”๊ฐ€ํ•œ๋‹ค.

#[test]
fn shipping_fee_at_threshold() {
    assert_eq!(shipping_fee(5000), 0);
}

์ž์‹์ธ tests module์—์„œ๋Š” ๋ถ€๋ชจ module์˜ private ํ•ญ๋ชฉ์„ ์‚ฌ์šฉํ•œ๋‹ค. use super::*;๋Š” ์ด ์ด๋ฆ„์„ ๊ฐ€์ ธ์˜ค๋Š” ๊ตฌ๋ฌธ์ด๋‹ค.

Integration test์—์„œ ๊ณต๊ฐœ API ํ˜ธ์ถœ

tests ๋””๋ ‰ํ„ฐ๋ฆฌ๋ฅผ ๋งŒ๋“ ๋‹ค.

mkdir -p tests

tests/api.rs์— ๋‹ค์Œ์„ ๋„ฃ๋Š”๋‹ค.

use shop_debug::{parse_price, total};
 
#[test]
fn entered_price_produces_order_total() {
    let price = parse_price(" 1000 ").unwrap();
    assert_eq!(total(price, 5), 5000);
}
 
#[test]
fn small_order_includes_shipping() {
    assert_eq!(total(1000, 3), 3300);
}

Integration test๋Š” ๋ณ„๋„ crate๋กœ ์ปดํŒŒ์ผํ•ด library๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค. ์™ธ๋ถ€์—์„œ ํ˜ธ์ถœํ•  ๊ณต๊ฐœ API์ธ parse_price์™€ total์„ ๊ฐ€์ ธ์˜จ๋‹ค.

cargo test --test api

Doc test๋กœ ์‚ฌ์šฉ๋ฒ• ํ™•์ธ

src/lib.rs์˜ total() ๋ฐ”๋กœ ์œ„์— ๋‹ค์Œ ๋ฌธ์„œ ์ฃผ์„์„ ๋ถ™์ธ๋‹ค.

/// ์ƒํ’ˆ ๊ธˆ์•ก๊ณผ ๋ฐฐ์†ก๋น„๋ฅผ ํ•ฉ์‚ฐํ•œ๋‹ค. ์ˆ˜๋Ÿ‰์€ ์–‘์ˆ˜์—ฌ์•ผ ํ•œ๋‹ค.
///
/// # Examples
///
/// ```
/// assert_eq!(shop_debug::total(1000, 3), 3300);
/// assert_eq!(shop_debug::total(1000, 5), 5000);
/// ```

๋ฌธ์„œ ํ…Œ์ŠคํŠธ๋Š” ๋ฌธ์„œ์˜ Rust ์˜ˆ์ œ๋ฅผ ์ปดํŒŒ์ผํ•˜๊ณ  ์‹คํ–‰ํ•œ๋‹ค. API๋ฅผ ๋ฐ”๊ฟจ์„ ๋•Œ ๋ฌธ์„œ์˜ ์‚ฌ์šฉ๋ฒ•๋„ ํ•จ๊ป˜ ํ™•์ธํ•œ๋‹ค.

# library์˜ ๋ฌธ์„œ ํ…Œ์ŠคํŠธ๋งŒ ์‹คํ–‰
cargo test --doc
 
# ๊ธฐ๋ณธ ๋Œ€์ƒ์˜ unitยทintegrationยทdoc test ์‹คํ–‰
cargo test

pytest ๋น„๊ต: ํ…Œ์ŠคํŠธ ๋ฐฐ์น˜์™€ ๋ฌธ์„œ ์˜ˆ์ œ

  • ํŒŒ์ผ ๋ฐฐ์น˜: pytest์—์„œ๋Š” ํ…Œ์ŠคํŠธ ํŒŒ์ผ์„ tests/test_pricing.py์ฒ˜๋Ÿผ ๋ฐฐ์น˜ํ•˜๊ณ  ํ•จ์ˆ˜๋ฅผ importํ•ด ํ…Œ์ŠคํŠธ. Rust์˜ ์†Œ์Šค ๋‚ด๋ถ€ unit test๋Š” private ํ•ญ๋ชฉ์— ์ ‘๊ทผํ•˜๋ฉฐ root์˜ tests/๋Š” ๋ณ„๋„ crate๋กœ ์ปดํŒŒ์ผํ•ด ๊ณต๊ฐœ API๋ฅผ ์‚ฌ์šฉ.
  • ๊ฒ€์ฆ ๋ฒ”์œ„: pytest๋Š” ์ž‘์€ ํ•จ์ˆ˜๋‚˜ ์—ฌ๋Ÿฌ ๊ธฐ๋Šฅ์˜ ์—ฐ๋™์„ ๊ฐ™์€ ์ˆ˜์ง‘ยท์‹คํ–‰ ๋ฐฉ์‹์œผ๋กœ ๊ฒ€์‚ฌ. Rust๋Š” unitยทintegration test์˜ crate ๊ฒฝ๊ณ„๋ฅผ ๊ตฌ๋ถ„.
  • ๋ฌธ์„œ ์˜ˆ์ œ: pytest์˜ --doctest-modules๋Š” module์˜ docstring ์˜ˆ์ œ๋ฅผ ์ˆ˜์ง‘. Rust doc test๋Š” library ๋ฌธ์„œ์˜ Rust ์˜ˆ์ œ๋ฅผ ์ปดํŒŒ์ผํ•˜๊ณ  ์‹คํ–‰.

7. ์ €์žฅ ์‹œ ํ…Œ์ŠคํŠธ ์ž๋™ ์‹คํ–‰

์ƒˆ ํ•จ์ˆ˜๋ฅผ ์ž‘์„ฑํ•˜๋‹ค ๋™์ž‘์ด ๊ถ๊ธˆํ•ด์ง€๋ฉด ๊ฐ™์€ ํŒŒ์ผ์˜ mod tests์— ์ž…๋ ฅ์„ ํ•˜๋‚˜ ๋„ฃ๋Š”๋‹ค. ํ•„์š”ํ•œ ๊ฐ’์— dbg!๋ฅผ ๋ถ™์ด๊ณ  ์ด ํ…Œ์ŠคํŠธ๋งŒ ๋ฐ˜๋ณตํ•ด์„œ ์‹คํ–‰ํ•œ๋‹ค.

cargo test --lib parsed_price_calculates_total -- --nocapture

๋ฐ˜๋ณตํ•˜๋Š” ๋ช…๋ น์€ ์ €์žฅํ•  ๋•Œ ์ž๋™ ์‹คํ–‰ํ•˜๋„๋ก ์„ค์ •ํ•œ๋‹ค. Bacon์„ ๋ณ„๋„ ํ„ฐ๋ฏธ๋„์— ์ผœ๋‘๋ฉด ์ฝ”๋“œ ๋ณ€๊ฒฝ์„ ๊ฐ์ง€ํ•ด ๊ฒ€์‚ฌยทํ…Œ์ŠคํŠธ๋ฅผ ๋‹ค์‹œ ์‹คํ–‰ํ•˜๊ณ  ๊ฒฐ๊ณผ๋ฅผ ํ‘œ์‹œํ•œ๋‹ค.

cargo install --locked bacon
bacon test
  • ํ™”๋ฉด ๊ตฌ์„ฑ: ํ•œ์ชฝ์—๋Š” ์—๋””ํ„ฐ, ๋‹ค๋ฅธ ์ชฝ์—๋Š” Bacon์„ ๋‘”๋‹ค.
  • ์‹คํŒจ ์ง‘์ค‘: Bacon์—์„œ ํ…Œ์ŠคํŠธ ์‹คํŒจ ํ›„ f๋ฅผ ๋ˆ„๋ฅด๋ฉด ์‹คํŒจํ•œ ํ…Œ์ŠคํŠธ์— ์ง‘์ค‘ํ•œ๋‹ค. Esc๋กœ ์ „์ฒด ํ…Œ์ŠคํŠธ๋กœ ๋Œ์•„๊ฐ„๋‹ค.
  • ์ž‘์—… ์„ ํƒ: ๊ธฐ๋ณธ ํ…Œ์ŠคํŠธ job ์™ธ์— ํŠน์ • targetยทํ•จ์ˆ˜๋งŒ ์‹คํ–‰ํ•  job์„ ์ง€์ •ํ•œ๋‹ค.

์ž‘์€ ํ…Œ์ŠคํŠธ๋งŒ ๋ฐ˜๋ณตํ•˜๋ ค๋ฉด ํ”„๋กœ์ ํŠธ root์˜ bacon.toml์— ๋‹ค์Œ job๊ณผ ํ‚ค ์„ค์ •์„ ์ถ”๊ฐ€ํ•œ๋‹ค. ํŒŒ์ผ์ด ์—†์œผ๋ฉด ์ƒˆ๋กœ ๋งŒ๋“ ๋‹ค.

[jobs.price]
command = ["cargo", "test", "--lib", "parsed_price_calculates_total", "--", "--nocapture"]
need_stdout = true
 
[keybindings]
ctrl-r = "toggle-raw-output"
bacon price

์ด์ œ ๊ฐ€๊ฒฉ ํŒŒ์‹ฑ์ด๋‚˜ ๊ณ„์‚ฐ ํ•จ์ˆ˜๋ฅผ ์ˆ˜์ •ํ•˜๊ณ  ์ €์žฅํ•˜๋ฉด ๊ฐ™์€ ํ…Œ์ŠคํŠธ๊ฐ€ ๋‹ค์‹œ ์‹คํ–‰๋œ๋‹ค. Ctrl+R๋กœ ์›๋ณธ ์ถœ๋ ฅ์„ ํ‘œ์‹œํ•˜๋ฉด ํ†ต๊ณผํ•œ ํ…Œ์ŠคํŠธ์˜ dbg! ์ถœ๋ ฅ๋„ ํ™•์ธํ•œ๋‹ค. ์‹คํ–‰ ์‹œ๊ฐ„์€ ํ”„๋กœ์ ํŠธ ๊ทœ๋ชจยท๋ณ€๊ฒฝ ๋ฒ”์œ„ยท๋นŒ๋“œ ์บ์‹œ์— ๋”ฐ๋ผ ๋‹ฌ๋ผ์ง„๋‹ค.

๊ธฐ์กด ๋„๊ตฌ: cargo-watch์˜ ์ž๋™ ์‹คํ–‰

  • ์—ญํ• : ํŒŒ์ผ ๋ณ€๊ฒฝ์„ ๊ฐ์ง€ํ•ด ์ง€์ •ํ•œ Cargo ๋ช…๋ น์„ ๋‹ค์‹œ ์‹คํ–‰.
  • ์œ ์ง€๋ณด์ˆ˜: cargo-watch ์ €์žฅ์†Œ๋Š” archive ์ƒํƒœ์ด๋ฉฐ ์ž‘์„ฑ์ž๋Š” Bacon์ด๋‚˜ Watchexec๋ฅผ ๊ถŒ์žฅ.
  • ๊ธฐ์กด ํ™˜๊ฒฝ: ์ด๋ฏธ ์„ค์น˜ํ•ด ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ ๋‹ค์Œ ๋ช…๋ น์œผ๋กœ ๊ฐ™์€ ํ…Œ์ŠคํŠธ๋ฅผ ๋ฐ˜๋ณต ์‹คํ–‰.
cargo watch -x 'test --lib parsed_price_calculates_total -- --nocapture'

ํšŒ๊ท€ ํ…Œ์ŠคํŠธ์™€ ์ž„์‹œ ์ถœ๋ ฅ ์ •๋ฆฌ

  1. ๊ถ๊ธˆํ•œ ๋™์ž‘์„ ์ž‘์€ ํ…Œ์ŠคํŠธ๋กœ ํ˜ธ์ถœํ•œ๋‹ค.
  2. dbg! ์ถœ๋ ฅ์œผ๋กœ ์ž…๋ ฅยท์ค‘๊ฐ„ ๊ฐ’ยท๋ถ„๊ธฐ๋ฅผ ํ™•์ธํ•œ๋‹ค.
  3. ๊ธฐ๋Œ€ํ•œ ๋™์ž‘์„ assertion์œผ๋กœ ์ž‘์„ฑํ•œ๋‹ค.
  4. ๊ตฌํ˜„์„ ๊ณ ์น˜๊ณ  ํ…Œ์ŠคํŠธ๊ฐ€ ํ†ต๊ณผํ•˜๋Š”์ง€ ํ™•์ธํ•œ๋‹ค.
  5. dbg!๋ฅผ ์ง€์šฐ๊ณ  ์žฌํ˜„ ํ…Œ์ŠคํŠธ๋ฅผ ๋‚จ๊ธด๋‹ค.

๋‚จ์€ ์ž„์‹œ ์ถœ๋ ฅ์€ Clippy์˜ dbg_macro ๊ฒ€์‚ฌ๋กœ ์ฐพ๋Š”๋‹ค.

cargo clippy --all-targets -- -W clippy::dbg_macro

--all-targets๋กœ ํ…Œ์ŠคํŠธ ๋Œ€์ƒ๋„ ๊ฒ€์‚ฌํ•œ๋‹ค. src/lib.rs ๋งจ ์œ„์— ๋‹ค์Œ ์†์„ฑ์„ ๋‘๋ฉด ์ด crate์—์„œ ํ•ญ์ƒ ๊ฒฝ๊ณ ํ•˜๋„๋ก ์„ค์ •ํ•œ๋‹ค.

#![warn(clippy::dbg_macro)]

pytest ๋น„๊ต: ์ €์žฅ ์‹œ ํ…Œ์ŠคํŠธ ์žฌ์‹คํ–‰

  • ์ž๋™ ์‹คํ–‰: pytest ํ™˜๊ฒฝ์˜ pytest-watcher์ฒ˜๋Ÿผ Baconยทcargo-watch๋„ ํŒŒ์ผ ๋ณ€๊ฒฝ์„ ๊ฐ์ง€ํ•ด ํ…Œ์ŠคํŠธ ๋ช…๋ น์„ ์žฌ์‹คํ–‰.
  • ์žฌํ˜„ ์‚ฌ๋ก€: ์–‘์ชฝ ๋ชจ๋‘ ์กฐ์‚ฌํ•  ์ž…๋ ฅ์„ ์ž‘์€ ํ…Œ์ŠคํŠธ๋กœ ๊ณ ์ •ํ•˜๊ณ  ์ˆ˜์ • ํ›„ ๊ฐ™์€ ๊ธฐ๋Œ€๊ฐ’์„ ๋‹ค์‹œ ๊ฒ€์ฆ.
  • ์ปดํŒŒ์ผ ๋‹จ๊ณ„: Rust๋Š” ๋ณ€๊ฒฝํ•œ ์ฝ”๋“œ๋ฅผ ์ปดํŒŒ์ผํ•œ ๋’ค ํ…Œ์ŠคํŠธ ์‹คํ–‰ ํŒŒ์ผ์„ ์‹คํ–‰.

8. panic ํ˜ธ์ถœ ๊ฒฝ๋กœ์™€ ์‹คํ–‰ ์ƒํƒœ ํ™•์ธ

์—ฌ๋Ÿฌ ํ•จ์ˆ˜๋ฅผ ๊ฑฐ์นœ ๋’ค panicํ–ˆ๋‹ค๋ฉด backtrace๋ฅผ ์ผœ ํ˜ธ์ถœ ๊ฒฝ๋กœ๋ฅผ ํ™•์ธํ•œ๋‹ค. ์ˆ˜๋Ÿ‰์ด 0์ธ ์ฃผ๋ฌธ์€ total()์˜ assertion์—์„œ panicํ•˜๋ฏ€๋กœ ์•ž์˜ ํ…Œ์ŠคํŠธ๋กœ ์ถœ๋ ฅ ๋ฐฉ์‹์„ ํ™•์ธํ•œ๋‹ค.

RUST_BACKTRACE=1 cargo test --lib zero_quantity_panics -- --nocapture
  • panic ๋ฉ”์‹œ์ง€: quantity must be positive์™€ assertion ์œ„์น˜๋ฅผ ํ™•์ธํ•œ๋‹ค.
  • ํ˜ธ์ถœ ๊ฒฝ๋กœ: ํ‘œ์ค€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์˜ panic ์ฒ˜๋ฆฌ ๋ถ€๋ถ„์„ ์ง€๋‚˜ shop_debug::total๊ณผ tests::zero_quantity_panics ๊ฐ™์€ ์ง์ ‘ ์ž‘์„ฑํ•œ ํ•จ์ˆ˜๋ฅผ ์ฐพ๋Š”๋‹ค.
  • ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ: ์ด ํ…Œ์ŠคํŠธ๋Š” #[should_panic]์œผ๋กœ ์˜ˆ์ƒํ•œ panic์„ ๊ฒ€์ฆํ•˜๋ฏ€๋กœ ํ†ต๊ณผํ•œ๋‹ค. panic ๋ฉ”์‹œ์ง€ ์ถœ๋ ฅ๊ณผ ํ…Œ์ŠคํŠธ ์‹คํŒจ๋Š” ๊ตฌ๋ถ„ํ•ด์„œ ์ฝ๋Š”๋‹ค.

์‹ค์ œ ์กฐ์‚ฌ์—์„œ๋Š” ๋ฌธ์ œ๋ฅผ ์ผ์œผํ‚จ ์‹คํ–‰ ๋ช…๋ น์ด๋‚˜ ํ…Œ์ŠคํŠธ์— ๊ฐ™์€ ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋ฅผ ๋ถ™์ธ๋‹ค. Backtrace๋Š” ํ˜ธ์ถœ ๊ฒฝ๋กœ๋ฅผ ๋ณด์—ฌ์ฃผ๋ฉฐ ๊ฐ ํ•จ์ˆ˜์˜ ๋ชจ๋“  ๋ณ€์ˆ˜ ๊ฐ’์„ ์ €์žฅํ•˜๋Š” ๊ธฐ๋Šฅ์€ ์•„๋‹ˆ๋‹ค.

Debugger์˜ breakpoint์™€ ์ƒํƒœ ํ™•์ธ

๋ฐ˜๋ณต๋ฌธ์ด๋‚˜ ์—ฌ๋Ÿฌ ํ•จ์ˆ˜์˜ ์ƒํƒœ๋ฅผ ํ•จ๊ป˜ ๋ด์•ผ ํ•œ๋‹ค๋ฉด breakpoint์—์„œ ์‹คํ–‰์„ ๋ฉˆ์ถ˜๋‹ค. macOS์—์„œ LLDB๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ํ™˜๊ฒฝ์—์„œ๋Š” ๋จผ์ € ์‹คํ–‰ ํŒŒ์ผ์„ ๋นŒ๋“œํ•œ๋‹ค.

cargo build
rust-lldb target/debug/shop_debug

rust-lldb๋Š” Rust ๋„๊ตฌ์ฒด์ธ์—์„œ ์ œ๊ณตํ•˜๋Š” LLDB ์‹คํ–‰ wrapper๋‹ค. LLDB๊ฐ€ ์„ค์น˜๋œ ํ™˜๊ฒฝ์—์„œ LLDB ๋ช…๋ น์œผ๋กœ ํ•จ์ˆ˜์— breakpoint๋ฅผ ์„ค์ •ํ•˜๊ณ  ์‹คํ–‰ํ•œ๋‹ค.

breakpoint set --name shop_debug::total
run
frame variable
next
thread backtrace
continue
quit
๋ช…๋ นํ™•์ธํ•˜๋Š” ๋‚ด์šฉ
breakpoint set --name ...์ด ํ•จ์ˆ˜์—์„œ ๋ฉˆ์ถ”๋„๋ก ์œ„์น˜ ์ง€์ •
runํ”„๋กœ๊ทธ๋žจ ์‹คํ–‰
frame variableํ˜„์žฌ stack frame์˜ ๋ณ€์ˆ˜ ํ™•์ธ
nextํ•จ์ˆ˜ ํ˜ธ์ถœ ์•ˆ์œผ๋กœ ๋“ค์–ด๊ฐ€์ง€ ์•Š๊ณ  ๋‹ค์Œ ์†Œ์Šค ์ค„๋กœ ์ด๋™
stepํ˜ธ์ถœํ•œ ํ•จ์ˆ˜ ์•ˆ์œผ๋กœ ์ง„์ž…ํ•˜๋ฉฐ ํ•œ ์ค„์”ฉ ์‹คํ–‰
thread backtraceํ˜„์žฌ thread์˜ ํ˜ธ์ถœ ์Šคํƒ ํ™•์ธ
continue๋‹ค์Œ breakpoint๋‚˜ ์ข…๋ฃŒ๊นŒ์ง€ ๊ณ„์† ์‹คํ–‰

total()์—์„œ ๋ฉˆ์ท„๋‹ค๋ฉด ์ธ์ˆ˜๋กœ ๋ฐ›์€ unit_price์™€ quantity๋ฅผ ํ™•์ธํ•œ๋‹ค. ๊ณ„์‚ฐ ์ค„์„ ์ง€๋‚œ ๋’ค์—๋Š” subtotal๋„ ํ™•์ธํ•˜๊ณ  ๊ฐ’์ด ์˜ˆ์ƒ๊ณผ ๋‹ฌ๋ผ์ง€๋Š” ์œ„์น˜๋ฅผ ์ฐพ๋Š”๋‹ค.

๋นŒ๋“œ ์„ค์ •: release์—์„œ๋„ ๊ฐ™์€ ๋™์ž‘์ธ์ง€ ํ™•์ธ

  • ์ตœ์ ํ™”: ์ตœ์ ํ™”ํ•œ ์‹คํ–‰ ํŒŒ์ผ์—์„œ๋Š” ์†Œ์Šค ์ค„๊ณผ ์‹คํ–‰ ์ˆœ์„œ๊ฐ€ ๋‹ฌ๋ผ์ง€๊ฑฐ๋‚˜ ์ผ๋ถ€ ๋ณ€์ˆ˜๊ฐ€ ๋ณด์ด์ง€ ์•Š์„ ์ˆ˜ ์žˆ์Œ.
  • Debug ์ •๋ณด: ๋ณ€์ˆ˜ยท์†Œ์Šค ์œ„์น˜ ์กฐ์‚ฌ์— ํ•„์š”ํ•œ ์ •๋ณด๋Š” Cargo profile์—์„œ ์„ค์ •.
  • ์ •์ˆ˜ overflow: overflow-checks๋ฅผ ์ผœ๋ฉด overflow์—์„œ panic. ๊ธฐ๋ณธ devยทtest์™€ release์˜ ์„ค์ •์ด ๋‹ฌ๋ผ ์‹คํ–‰ ๊ฒฐ๊ณผ๋ฅผ ๋น„๊ตํ•  ๋•Œ ํ™•์ธ.
  • ๋นŒ๋“œ์— ๋…๋ฆฝ์ ์ธ ๊ณ„์•ฝ: ์˜ˆ์ œ์˜ assert!(quantity > 0, ...)๋Š” release์—์„œ๋„ ์‹คํ–‰. debug_assert!๋Š” ๊ธฐ๋ณธ release ์„ค์ •์—์„œ ๊ฒ€์‚ฌํ•˜์ง€ ์•Š์Œ.

release ํ…Œ์ŠคํŠธ๋„ ํ™•์ธํ•œ๋‹ค.

cargo test --release

pytest ๋น„๊ต: ์‹คํŒจ ๊ฒฝ๋กœ์™€ ์‹คํ–‰ ์ƒํƒœ ์กฐ์‚ฌ

  • ์‹คํŒจ ๊ฒฝ๋กœ: pytest๋Š” ์‹คํŒจ ๋ณด๊ณ ์— traceback์„ ํ‘œ์‹œ. Rust์˜ panic์€ RUST_BACKTRACE=1๋กœ ํ˜ธ์ถœ ์Šคํƒ ํ™•์ธ.
  • ์ค‘๋‹จ ์กฐ์‚ฌ: pytest์˜ --pdb๋Š” ์‹คํŒจ ์‹œ pdb์— ์ง„์ž…. Rust ์˜ˆ์ œ๋Š” ๋ณ„๋„ debugger์ธ LLDB๋กœ ์‹คํ–‰ ํŒŒ์ผ์„ ์กฐ์‚ฌ.
  • ์‹คํ–‰ ๋Œ€์ƒ: pytest๋Š” Python ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•˜๋ฉฐ LLDB๋Š” ์ปดํŒŒ์ผํ•œ Rust ์‹คํ–‰ ํŒŒ์ผ์„ ์‚ฌ์šฉ. ์†Œ์Šค๋ฅผ ์ˆ˜์ •ํ•˜๋ฉด ๋‹ค์‹œ ๋นŒ๋“œ.

9. ํ…Œ์ŠคํŠธ ๋Œ€์ƒ๊ณผ ํŒŒ์ผ ๊ตฌ์„ฑ

๊ฐœ๋ฐœ ์ค‘์—๋Š” ์ˆ˜์ •ํ•œ ํ•จ์ˆ˜์˜ ํ…Œ์ŠคํŠธ๋ฅผ ๋จผ์ € ์‹คํ–‰ํ•˜๊ณ  ๋ณ€๊ฒฝ์„ ๋งˆ๋ฌด๋ฆฌํ•  ๋•Œ ์ „์ฒด ํ…Œ์ŠคํŠธ๋ฅผ ์‹คํ–‰ํ•œ๋‹ค. ์ด๋ฆ„ ํ•„ํ„ฐ๋Š” ์‹คํ–‰ํ•  ํ•จ์ˆ˜๋ฅผ ์ค„์ด๊ณ  target ์„ ํƒ์€ ๋นŒ๋“œํ•  ๋ฒ”์œ„๋„ ์ค„์ด๋Š” ์—ญํ• ์„ ํ•œ๋‹ค.

๊ณ„์‚ฐ ๋กœ์ง๊ณผ CLI๋ฅผ ๋ถ„๋ฆฌํ•˜๋Š” ์ด์œ 

ํ˜„์žฌ src/lib.rs์˜ ๊ณ„์‚ฐยทํŒŒ์‹ฑ ๊ธฐ๋Šฅ์€ unit test๋กœ ํ˜ธ์ถœํ•˜๊ฑฐ๋‚˜ integration test์—์„œ importํ•œ๋‹ค. src/main.rs๋Š” library๋ฅผ ํ˜ธ์ถœํ•ด ์ถœ๋ ฅํ•˜๋Š” ์—ญํ• ์„ ๋งก๋Š”๋‹ค.

  • Library์˜ unit test: ๊ฐ€๊ฒฉ ๊ณ„์‚ฐยทํŒŒ์‹ฑ ๊ทœ์น™์„ ์ž‘์€ ์ž…๋ ฅ์œผ๋กœ ํ™•์ธํ•œ๋‹ค.
  • Library์˜ integration test: ์‚ฌ์šฉ์ž๊ฐ€ ํ˜ธ์ถœํ•˜๋Š” ๊ณต๊ฐœ API ๊ฒฝ๊ณ„๋ฅผ ๊ฒ€์‚ฌํ•œ๋‹ค.
  • CLI์˜ integration test: ํ•„์š”ํ•˜๋ฉด ์‹คํ–‰ ํŒŒ์ผ์„ ๋ณ„๋„ ํ”„๋กœ์„ธ์Šค๋กœ ์‹คํ–‰ํ•ด ์ถœ๋ ฅยท์ข…๋ฃŒ ์ƒํƒœ๋ฅผ ๊ฒ€์‚ฌํ•œ๋‹ค.

Binary crate: main.rs์—๋„ unit test ์ž‘์„ฑ ๊ฐ€๋Šฅ

  • ๊ฐ™์€ crate์˜ ํ…Œ์ŠคํŠธ: main.rs๋‚˜ ๊ทธ ํ•˜์œ„ module์—๋„ #[cfg(test)] mod tests๋ฅผ ๋ฐฐ์น˜.
  • Library ๋ถ„๋ฆฌ์˜ ์ด์ : ๊ณต๊ฐœ Rust API๋ฅผ ๋‹ค๋ฅธ crate์—์„œ importํ•˜๊ณ  ์—ฌ๋Ÿฌ binary์—์„œ ๊ณต์œ ํ•˜๊ธฐ ์‰ฌ์›Œ์ง.
  • CLI ์‹คํ–‰: binary๋ฅผ library์ฒ˜๋Ÿผ importํ•˜๋Š” ๋Œ€์‹  ์‹คํ–‰ ํŒŒ์ผ์„ ํ˜ธ์ถœํ•˜๋Š” integration test๋„ ๊ฐ€๋Šฅ.

Integration test target ํ†ตํ•ฉ

๊ธฐ๋ณธ Cargo ๊ตฌ์กฐ์—์„œ๋Š” tests/api.rs, tests/parsing.rs๊ฐ€ ๊ฐ๊ฐ ๋ณ„๋„ ์‹คํ–‰ ํŒŒ์ผ์ด ๋œ๋‹ค. ํŒŒ์ผ์ด ๋งŽ์•„์ ธ ๋นŒ๋“œยท๋งํฌ ๋น„์šฉ์ด ์ปค์กŒ๋‹ค๋ฉด ํ•˜๋‚˜์˜ target ์•„๋ž˜์— module๋กœ ๋ฌถ๋Š” ๊ตฌ์„ฑ์„ ๊ฒ€ํ† ํ•œ๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด api.rs์™€ parsing.rs๊ฐ€ ์ƒ๊ธด ํ”„๋กœ์ ํŠธ๋ฅผ ๋‹ค์Œ์ฒ˜๋Ÿผ ๋‚˜๋ˆˆ๋‹ค. ์ด ๊ธ€์˜ ์ž‘์€ ์˜ˆ์ œ์—์„œ๋Š” ๊ธฐ์กด tests/api.rs๋ฅผ ์œ ์ง€ํ•ด๋„ ์ถฉ๋ถ„ํ•˜๋‹ค.

tests/
โ””โ”€โ”€ it/
    โ”œโ”€โ”€ main.rs
    โ”œโ”€โ”€ api.rs
    โ””โ”€โ”€ parsing.rs

tests/it/main.rs๋Š” ๋‹ค์Œ module์„ ์—ฐ๊ฒฐํ•œ๋‹ค.

mod api;
mod parsing;

๋ณ„๋„ crate์˜ ์ˆ˜๋ฅผ ์ค„์ด๊ณ  ๊ฐ module์˜ ํ…Œ์ŠคํŠธ๋Š” ๊ฐ™์€ ์‹คํ–‰ ํŒŒ์ผ์—์„œ ์‹คํ–‰ํ•œ๋‹ค. ํ•จ์ˆ˜๋Š” ์—ฌ์ „ํžˆ ์ด๋ฆ„์ด๋‚˜ module ๊ฒฝ๋กœ๋กœ ๊ณจ๋ผ ์‹คํ–‰ํ•œ๋‹ค.

๊ณต์œ  helper์˜ ์œ„์น˜

์—ฌ๋Ÿฌ integration test๊ฐ€ ๊ฐ™์€ fixture๋‚˜ ์ค€๋น„ ์ฝ”๋“œ๋ฅผ ์“ด๋‹ค๋ฉด ๊ณต์œ  module์„ ๋‘”๋‹ค.

tests/
โ”œโ”€โ”€ api.rs
โ”œโ”€โ”€ parsing.rs
โ””โ”€โ”€ common/
    โ””โ”€โ”€ mod.rs
  • ์—ฐ๊ฒฐ: ๊ฐ ํ…Œ์ŠคํŠธ ํŒŒ์ผ์—์„œ mod common;์„ ์„ ์–ธํ•ด helper ์‚ฌ์šฉ.
  • ์œ„์น˜: tests/common.rs๋กœ ๋‘๋ฉด ๊ธฐ๋ณธ target ํƒ์ƒ‰์—์„œ ๋ณ„๋„ integration test๋กœ ์žกํž˜. tests/common/mod.rs๋Š” helper module๋กœ ์—ฐ๊ฒฐ.
  • ์†๋„ ํŒ๋‹จ: ํŒŒ์ผ์„ ๋ฌถ๋Š” ํšจ๊ณผ๋Š” ๋นŒ๋“œยท๋งํฌ ์‹œ๊ฐ„์— ๋”ฐ๋ผ ๋‹ค๋ฆ„. ์ž‘์€ ํ…Œ์ŠคํŠธ ๋ชจ์Œ๋ถ€ํ„ฐ ๋ณต์žกํ•˜๊ฒŒ ์žฌ๊ตฌ์„ฑํ•  ํ•„์š”๋Š” ์ ์Œ.

10. ๊ฒ€์ฆ ๋Œ€์ƒ๋ณ„ ๋„๊ตฌ ์„ ํƒ

์•ž์˜ ์˜ˆ์ œ๋Š” ํ‘œ์ค€ ํ…Œ์ŠคํŠธ ์‹คํ–‰๊ธฐ๋งŒ ์‚ฌ์šฉํ•œ๋‹ค. Async ์‹คํ–‰ ํ™˜๊ฒฝ์ด๋‚˜ ํฐ ์ถœ๋ ฅ, ๋‹ค์–‘ํ•œ ์ž…๋ ฅ์„ ๊ฒ€์‚ฌํ•  ํ•„์š”๊ฐ€ ์ƒ๊ธฐ๋ฉด ๋ชฉ์ ์— ๋งž๋Š” ๋„๊ตฌ๋ฅผ ์ถ”๊ฐ€ํ•œ๋‹ค.

๋„๊ตฌ์‚ฌ์šฉํ•  ์ƒํ™ฉ๊ฒ€์ฆ ๋ฐฉ์‹
#[tokio::test]Tokio ๊ธฐ๋ฐ˜ async ์ฝ”๋“œํ…Œ์ŠคํŠธ์šฉ runtime์„ ๊ตฌ์„ฑํ•ด async ํ•จ์ˆ˜ ์‹คํ–‰
cargo-nextestํ…Œ์ŠคํŠธ ์‹คํ–‰ยท์„ ํƒยท๊ฒฐ๊ณผ ๋ณด๊ณ  ๊ด€๋ฆฌunitยทintegration test ์‹คํ–‰ ๋ฐฉ์‹์„ ํ™•์žฅ
instaํŒŒ์„œยทํฌ๋งคํ„ฐ ๋“ฑ์˜ ํฐ ์ถœ๋ ฅ์ €์žฅํ•œ snapshot๊ณผ ํ˜„์žฌ ์ถœ๋ ฅ์„ ๋น„๊ต
proptest์—ฌ๋Ÿฌ ์ž…๋ ฅ์—์„œ ๊ทœ์น™์ด ์œ ์ง€๋˜๋Š”์ง€ ํ™•์ธ์ž…๋ ฅ์„ ์ƒ์„ฑํ•ด ๊ฒ€์ฆํ•˜๊ณ  ์‹คํŒจํ•œ ์ž…๋ ฅ์„ ๋‹จ์ˆœํ™”

์˜ˆ๋ฅผ ๋“ค์–ด ๋ฐฐ์†ก๋น„ ๊ทœ์น™์„ ์—ฌ๋Ÿฌ ๊ธˆ์•ก์œผ๋กœ ํ™•์ธํ•˜๋ ค๋ฉด proptest๋กœ ํ•ฉ๊ณ„๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ๊ทœ์น™์„ ๊ฒ€์‚ฌํ•œ๋‹ค. ์ฃผ๋ฌธ ๋ช…์„ธ์„œ๋ฅผ ๋ฌธ์ž์—ด๋กœ ์ถœ๋ ฅํ•˜๋Š” ๊ธฐ๋Šฅ์ด๋ผ๋ฉด insta๋กœ ์ถœ๋ ฅ ์ „์ฒด์˜ ๋ณ€๊ฒฝ์„ ๋น„๊ตํ•œ๋‹ค.

Nextest ๋ฒ”์œ„: ๋ฌธ์„œ ํ…Œ์ŠคํŠธ๋Š” ๋ณ„๋„ ์‹คํ–‰

  • ์‹คํ–‰ ๋Œ€์ƒ: Nextest๋Š” unitยทintegration test๋ฅผ ์‹คํ–‰. ๋ฌธ์„œ ํ…Œ์ŠคํŠธ๋Š” ํ˜„์žฌ ๋ณ„๋„ Cargo ๋ช…๋ น์œผ๋กœ ์‹คํ–‰.
  • ์†๋„: ๋ณ‘๋ ฌ ์‹คํ–‰ ๋“ฑ์˜ ํšจ๊ณผ๋Š” ํ…Œ์ŠคํŠธ ๊ตฌ์„ฑ๊ณผ ์‹คํ–‰ ์‹œ๊ฐ„์— ๋”ฐ๋ผ ๋‹ฌ๋ผ์ง.
# Nextest๋ฅผ ์„ค์น˜ํ•ด ์‚ฌ์šฉํ•˜๋Š” ํ™˜๊ฒฝ
cargo nextest run
cargo test --doc