0Pricing
Linux Command Line & Bash Scripting Mastery · บทเรียน

การทดสอบฟังก์ชันแบบยูนิตด้วย Bats-core

จัดโครงสร้างไฟล์ทดสอบ การตรวจยืนยัน และการตั้งค่า/เก็บกวาด เพื่อยืนยันฟังก์ชัน Bash แต่ละรายการ

การทดสอบฟังก์ชันแบบยูนิตด้วย Bats-core เป็นบทเรียน Linux Command Line & Bash Scripting Mastery ฟรีบน CoddyKit นี่คือบทเรียนที่ 1 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน Linux Command Line & Bash Scripting Mastery และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส Linux Command Line & Bash Scripting Mastery มีบทเรียนทั้งหมด 4 บทเรียน

Bats-core คืออะไรและเหตุใดจึงควรใช้

Bats-core (Bash Automated Testing System) เป็นเฟรมเวิร์กการทดสอบหน่วยสำหรับ Bash ที่ถือเป็นมาตรฐานโดยพฤตินัย ช่วยให้คุณเขียนการทดสอบฟังก์ชันเชลล์และสคริปต์อย่างเป็นโครงสร้าง ทำซ้ำได้ โดยใช้แนวทางเดียวกับการใช้ JUnit สำหรับ Java หรือ pytest สำหรับ Python

  • การทดสอบแต่ละรายการเป็นบล็อก @test พร้อมคำอธิบายที่อ่านเข้าใจได้
  • การทดสอบจะผ่านเมื่อทุกคำสั่งภายในคืนค่ารหัสการออกเป็น 0
  • การทดสอบจะไม่ผ่านเมื่อพบรหัสการออกที่ไม่ใช่ศูนย์เป็นรายการแรก หรือเมื่อการตรวจสอบยืนยันไม่ผ่าน
  • ผลลัพธ์เข้ากันได้กับ TAP ดังนั้นระบบ CI (GitHub Actions, Jenkins, GitLab CI) จึงเข้าใจได้โดยตรง

ติดตั้งผ่านตัวจัดการแพ็กเกจของคุณ หรือโคลนที่เก็บข้อมูล:

# Install via git (recommended — always latest)
git clone https://github.com/bats-core/bats-core.git
cd bats-core && sudo ./install.sh /usr/local

# Or on macOS with Homebrew
brew install bats-core

# Verify installation
bats --version
# bats 1.x.y

ไฟล์การทดสอบ Bats แรกของคุณ

ไฟล์การทดสอบ Bats มีนามสกุล .bats และเริ่มต้นด้วย shebang พิเศษ องค์ประกอบพื้นฐานคือคำสั่ง @test ตามด้วยสตริงคำอธิบายและบล็อกคำสั่ง

  • Shebang #!/usr/bin/env bats บอกเชลล์ว่าควรเรียกใช้ไฟล์นี้อย่างไร
  • บล็อก @test แต่ละบล็อกเป็นกรณีการทดสอบอิสระ
  • คุณสามารถเรียกใช้ไฟล์เดียวด้วย bats my_tests.bats หรือเรียกใช้ทั้งไดเรกทอรีด้วย bats test/

ด้านล่างนี้คือโครงสร้างขั้นต่ำของไฟล์การทดสอบ Bats:

#!/usr/bin/env bats
# File: test/hello.bats

@test "echo outputs the expected string" {
  result=$(echo "hello world")
  [ "$result" = "hello world" ]
}

@test "false command causes test to fail" {
  # Uncommenting the next line would make this test fail:
  # false
  true
}

โหลดฟังก์ชันที่กำลังทดสอบด้วย 'load'

ในโครงการจริง ฟังก์ชัน Bash ของคุณจะอยู่ในไฟล์ไลบรารี ไม่ได้อยู่ในไฟล์การทดสอบเอง Bats มีตัวช่วย load สำหรับอ่านไฟล์ภายนอกที่อยู่โดยสัมพันธ์กับไดเรกทอรีของไฟล์การทดสอบ

  • load '../lib/math.sh' จะอ่านไฟล์ดังกล่าวก่อนเริ่มการทดสอบแต่ละรายการ
  • หลังจากโหลดแล้ว ฟังก์ชันทั้งหมดที่กำหนดไว้ในไฟล์นั้นจะพร้อมใช้งานในบล็อกการทดสอบของคุณ
  • จัดเก็บฟังก์ชันไลบรารีไว้ในไดเรกทอรี lib/ และการทดสอบไว้ในไดเรกทอรี test/ เพื่อแยกส่วนอย่างชัดเจน

ตัวอย่างโครงสร้างโครงการและการทดสอบที่สอดคล้องกัน:

# Project layout:
# lib/math.sh       <- functions to test
# test/math.bats    <- test file

# lib/math.sh
add() {
  echo $(( $1 + $2 ))
}

divide() {
  if [ "$2" -eq 0 ]; then
    echo "Error: division by zero" >&2
    return 1
  fi
  echo $(( $1 / $2 ))
}

# test/math.bats
#!/usr/bin/env bats

load '../lib/math.sh'

@test "add returns correct sum" {
  result=$(add 3 4)
  [ "$result" = "7" ]
}

การตรวจสอบยืนยันหลัก: run, $status, $output

คำสั่ง run เป็นหัวใจของการทดสอบด้วย Bats แทนที่จะเรียกใช้คำสั่งโดยตรง การครอบคำสั่งด้วย run จะบันทึกรหัสการออกและผลลัพธ์ไว้ โดยไม่ทำให้การทดสอบไม่ผ่านทันที

  • $status — เก็บรหัสการออกของคำสั่ง run ล่าสุด
  • $output — เก็บเอาต์พุตมาตรฐานที่รวมกันของคำสั่ง run ล่าสุด
  • $lines — อาร์เรย์ที่สมาชิกแต่ละตัวเป็นผลลัพธ์หนึ่งบรรทัด (${lines[0]}, ${lines[1]} และอื่น ๆ)

วิธีนี้ช่วยให้คุณตรวจสอบยืนยันได้ทั้งกรณีสำเร็จและกรณีล้มเหลว:

#!/usr/bin/env bats

load '../lib/math.sh'

@test "divide 10 by 2 returns 5" {
  run divide 10 2
  [ "$status" -eq 0 ]
  [ "$output" = "5" ]
}

@test "divide by zero returns exit code 1" {
  run divide 10 0
  [ "$status" -eq 1 ]
}

@test "divide by zero prints error message" {
  run divide 10 0
  # $output captures stderr too when redirected inside the function
  [[ "$output" == *"division by zero"* ]]
}

ใช้ bats-assert เพื่อการตรวจสอบยืนยันที่สื่อความหมาย

การตรวจสอบยืนยันในตัวด้วย [ ] ใช้งานได้ แต่ข้อความเมื่อไม่ผ่านมีรายละเอียดน้อย ไลบรารีตัวช่วย bats-assert มีฟังก์ชันตรวจสอบยืนยันที่สื่อความหมาย และแสดงให้เห็นอย่างชัดเจนว่าเกิดข้อผิดพลาดตรงไหน

  • assert_success — ตรวจสอบยืนยันว่า $status เป็น 0
  • assert_failure — ตรวจสอบยืนยันว่า $status ไม่ใช่ศูนย์
  • assert_output — ตรวจสอบยืนยันว่า $output เท่ากับสตริงที่ระบุ
  • assert_output --partial — ตรวจสอบยืนยันว่าผลลัพธ์มีสตริงย่อยดังกล่าว
  • refute_output --partial — ตรวจสอบยืนยันว่าผลลัพธ์ไม่มีสตริงย่อยดังกล่าว

ติดตั้งโดยโคลน bats-core/bats-assert ลงในโฟลเดอร์ test/helpers/ แล้วโหลดไลบรารี:

#!/usr/bin/env bats

# Load bats-assert (cloned into test/helpers/bats-assert)
load 'helpers/bats-assert/load'
load '../lib/math.sh'

@test "add 5 and 3 gives 8" {
  run add 5 3
  assert_success
  assert_output "8"
}

@test "divide by zero fails with descriptive message" {
  run divide 9 0
  assert_failure
  assert_output --partial "division by zero"
}

@test "add does not output an error" {
  run add 1 1
  refute_output --partial "Error"
}

setup และ teardown: จุดเชื่อมต่อวงจรชีวิตการทดสอบ

Bats มีฟังก์ชันพิเศษสองรายการ ได้แก่ setup และ teardown ซึ่งจะทำงานโดยอัตโนมัติก่อนและหลังการทดสอบแต่ละรายการ ใช้ฟังก์ชันเหล่านี้เพื่อเตรียมและล้างสถานะที่ใช้ร่วมกัน เพื่อให้การทดสอบแต่ละรายการเริ่มต้นจากสภาพแวดล้อมที่ทราบแน่นอน

  • setup() ทำงาน ก่อน บล็อก @test แต่ละบล็อก
  • teardown() ทำงาน หลัง บล็อก @test แต่ละบล็อก แม้การทดสอบจะไม่ผ่าน
  • การใช้งานทั่วไป ได้แก่ การสร้างไดเรกทอรีชั่วคราว การกำหนดตัวแปรสภาพแวดล้อม และการลบไฟล์ชั่วคราวหลังการทดสอบ
#!/usr/bin/env bats

load '../lib/fileutils.sh'

setup() {
  # Create a fresh temp directory before every test
  TEST_DIR=$(mktemp -d)
  export TEST_DIR
}

teardown() {
  # Always clean up, even on test failure
  rm -rf "$TEST_DIR"
}

@test "write_file creates a file with correct content" {
  run write_file "$TEST_DIR/hello.txt" "hello world"
  assert_success
  [ -f "$TEST_DIR/hello.txt" ]
  [ "$(cat "$TEST_DIR/hello.txt")" = "hello world" ]
}

@test "write_file fails when directory does not exist" {
  run write_file "/nonexistent/dir/file.txt" "data"
  assert_failure
}

setup_file และ teardown_file: จุดเชื่อมต่อระดับชุดการทดสอบ

บางครั้งคุณจำเป็นต้องเตรียมทรัพยากรที่ใช้เวลานานเพียงครั้งเดียวต่อไฟล์ ไม่ใช่ก่อนการทดสอบทุกครั้ง Bats มี setup_file และ teardown_file สำหรับจุดประสงค์นี้

  • setup_file() ทำงานหนึ่งครั้งก่อนการทดสอบ ทั้งหมด ในไฟล์
  • teardown_file() ทำงานหนึ่งครั้งหลังการทดสอบ ทั้งหมด ในไฟล์
  • ใช้ BATS_FILE_TMPDIR (พร้อมใช้งานโดยอัตโนมัติ) เพื่อแบ่งปันข้อมูลระหว่าง setup_file กับการทดสอบของคุณ — ตัวแปรทั่วไปจะไม่คงอยู่ข้ามเชลล์ย่อย

กรณีใช้งานทั่วไปคือ การเริ่มเซิร์ฟเวอร์จำลองหรือสร้างไบนารีเพียงครั้งเดียว แล้วหยุดการทำงานเมื่อเสร็จสิ้น:

#!/usr/bin/env bats

setup_file() {
  # Build the project binary once for all tests in this file
  make build --silent
  export BINARY="$PWD/bin/myapp"
  echo "Binary built: $BINARY"
}

teardown_file() {
  # Remove the binary after all tests complete
  rm -f "$BINARY"
  echo "Cleaned up binary"
}

setup() {
  # Still runs before each individual test
  TEST_TMP=$(mktemp -d)
}

teardown() {
  rm -rf "$TEST_TMP"
}

@test "myapp --version outputs version string" {
  run "$BINARY" --version
  assert_output --partial "1.0"
}

การทดสอบฟังก์ชันที่แก้ไขไฟล์

รูปแบบที่พบได้บ่อยมากคือการทดสอบฟังก์ชัน Bash ที่อ่านจากหรือเขียนลงระบบไฟล์ เทคนิคสำคัญคือการใช้ ไดเรกทอรีชั่วคราว (ผ่าน mktemp -d ใน setup) เพื่อให้การทดสอบไม่แตะต้องไฟล์จริงและไม่รบกวนกัน

  • ทำงานภายใน $TEST_DIR เสมอ (หรือ $BATS_TEST_TMPDIR ซึ่งพร้อมใช้งานโดยอัตโนมัติใน Bats รุ่นใหม่)
  • ใช้ไลบรารีตัวช่วย bats-file สำหรับการตรวจสอบไฟล์ที่อ่านเข้าใจง่าย เช่น assert_file_exists และ assert_file_contains
  • อย่ากำหนดเส้นทางตายตัว เช่น /tmp/myfile เพราะการทดสอบแบบขนานจะชนกัน
#!/usr/bin/env bats

load 'helpers/bats-assert/load'
load 'helpers/bats-file/load'
load '../lib/fileutils.sh'

setup() {
  TEST_DIR="$BATS_TEST_TMPDIR"
}

# lib/fileutils.sh defines:
# append_line() { echo "$2" >> "$1"; }

@test "append_line adds a line to an existing file" {
  echo "first line" > "$TEST_DIR/log.txt"

  run append_line "$TEST_DIR/log.txt" "second line"
  assert_success

  assert_file_contains "$TEST_DIR/log.txt" "second line"
}

@test "append_line creates file if it does not exist" {
  run append_line "$TEST_DIR/new.txt" "hello"
  assert_success
  assert_file_exists "$TEST_DIR/new.txt"
}

การจำลองคำสั่งภายนอก

ฟังก์ชันมักเรียกโปรแกรมภายนอก เช่น curl, aws หรือ git ในการทดสอบหน่วย คุณต้องการทดสอบตรรกะของคุณ ไม่ใช่คำสั่งภายนอกจริง เทคนิคการจำลองด้วย Bats ที่สะอาดที่สุดคือกำหนดฟังก์ชันเชลล์ที่มีชื่อเดียวกับคำสั่งไว้ใน setup ฟังก์ชันนี้จะมีลำดับความสำคัญเหนือไบนารีจริง

  • กำหนดฟังก์ชัน เช่น curl() { echo 'mocked response'; return 0; } ใน setup แล้วส่งออกฟังก์ชันนั้น
  • ใช้ export -f curl เพื่อให้ฟังก์ชันมองเห็นได้ในเชลล์ย่อยที่สร้างโดย run
  • คุณยังสามารถเขียนตัวจำลองลงในไฟล์ชั่วคราวบน PATH สำหรับกรณีที่ซับซ้อนยิ่งขึ้น
#!/usr/bin/env bats

load 'helpers/bats-assert/load'
load '../lib/network.sh'

# lib/network.sh defines:
# fetch_status() {
#   local url="$1"
#   local code
#   code=$(curl -s -o /dev/null -w "%{http_code}" "$url")
#   echo "$code"
# }

setup() {
  # Override 'curl' with a mock function
  curl() {
    # Simulate a 200 OK response
    echo "200"
    return 0
  }
  export -f curl
}

@test "fetch_status returns 200 when curl reports 200" {
  run fetch_status "https://example.com"
  assert_success
  assert_output "200"
}

การข้ามการทดสอบและการติดแท็ก

การทดสอบไม่ได้ทำงานได้ทุกครั้ง บางครั้งคุณจำเป็นต้องมีการเชื่อมต่อเครือข่ายจริง เครื่องมือเฉพาะ หรือ OS บางประเภท Bats มี skip เพื่อข้ามการทดสอบตามเงื่อนไขพร้อมข้อความอธิบาย แทนการใส่เครื่องหมายคอมเมนต์หรือตัดชุดการทดสอบให้เสียหาย

  • เรียก skip "reason" ที่ใดก็ได้ภายในบล็อก @test เพื่อข้ามการทดสอบนั้น
  • การทดสอบที่ถูกข้ามจะแสดงในผลลัพธ์เป็น S และไม่นับเป็นความล้มเหลว
  • Bats 1.5 ขึ้นไปรองรับ แท็ก: ใส่คำอธิบายประกอบให้การทดสอบด้วย # bats test_tags=slow,network และกรองด้วย bats --filter-tags network test/
#!/usr/bin/env bats

# bats test_tags=network
@test "API returns valid JSON" {
  # Skip if no internet connectivity
  if ! ping -c1 -W1 8.8.8.8 &>/dev/null; then
    skip "No network connection available"
  fi

  run curl -s "https://api.example.com/health"
  assert_success
  assert_output --partial '"status"'
}

# bats test_tags=unit
@test "slug function lowercases and replaces spaces" {
  # Always runs — pure function, no external deps
  slug() { echo "$1" | tr '[:upper:]' '[:lower:]' | tr ' ' '-'; }
  run slug "Hello World"
  assert_output "hello-world"
}

# Run only unit tests:
# bats --filter-tags unit test/

การจัดโครงสร้างชุดการทดสอบที่สมบูรณ์

โครงการ Bats ที่จัดระเบียบอย่างดีจะมีโครงสร้างไดเรกทอรีที่คาดเดาได้ ทำให้ผู้ร่วมพัฒนารายใหม่เริ่มต้นได้ง่าย และผสานรวมกับไปป์ไลน์ CI ได้สะดวก

โครงสร้างที่แนะนำ:

  • lib/ — ฟังก์ชัน Bash สำหรับใช้งานจริง (หนึ่งไฟล์ต่อหนึ่งหน้าที่: math.sh, fileutils.sh)
  • test/ — หนึ่งไฟล์ .bats ต่อหนึ่งไฟล์ไลบรารี (math.bats, fileutils.bats)
  • test/helpers/ — bats-assert, bats-file และ bats-support ในรูปแบบโมดูลย่อยของ git
  • Makefile — มีเป้าหมาย test เพื่อให้ผู้ร่วมพัฒนาเพียงเรียกใช้ make test

เรียกใช้ชุดการทดสอบทั้งหมดด้วยคำสั่งเดียว:

# Makefile
.PHONY: test
test:
	bats test/

# Run all tests recursively (Bats 1.5+)
# bats --recursive test/

# Run a specific file
# bats test/math.bats

# Run with verbose (TAP) output for CI
# bats --tap test/

# Example directory tree:
# .
# |-- lib/
# |   |-- math.sh
# |   `-- fileutils.sh
# |-- test/
# |   |-- helpers/
# |   |   |-- bats-assert/
# |   |   `-- bats-file/
# |   |-- math.bats
# |   `-- fileutils.bats
# `-- Makefile

ตรวจสอบความรู้: การตรวจสอบยืนยันของ Bats-core

ทดสอบความเข้าใจของคุณเกี่ยวกับกลไกการทดสอบหลักของ Bats-core

สรุปทบทวน: การทดสอบหน่วยของ Bash ด้วย Bats-core

ในบทเรียนนี้ คุณได้เรียนรู้วิธีจัดโครงสร้างและเขียนการทดสอบหน่วยสำหรับฟังก์ชัน Bash แต่ละรายการโดยใช้ Bats-core ประเด็นสำคัญมีดังนี้:

  • โครงสร้างไฟล์การทดสอบ — ใช้ shebang #!/usr/bin/env bats และบล็อก @test ที่มีชื่อสื่อความหมาย
  • load — อ่านไฟล์ไลบรารีของคุณเพื่อให้ฟังก์ชันพร้อมใช้งานในการทดสอบโดยไม่ต้องคัดลอกและวาง
  • run + $status + $output — ชุดหลักสามอย่าง ให้ใช้ run เพื่อบันทึกผลลัพธ์โดยไม่ทำให้การทดสอบไม่ผ่านทันที
  • bats-assert — เลือกใช้ assert_success, assert_failure และ assert_output แทน [ ] แบบพื้นฐาน เพื่อให้ข้อความเมื่อไม่ผ่านอ่านเข้าใจง่าย
  • setup / teardown — ทำงานก่อนและหลังการทดสอบแต่ละรายการ ส่วน setup_file / teardown_file ทำงานครั้งเดียวต่อไฟล์
  • การจำลอง — แทนที่คำสั่งภายนอกด้วยฟังก์ชันเชลล์ชื่อเดียวกันที่ส่งออกด้วย export -f
  • skip — ข้ามการทดสอบตามเงื่อนไขเมื่อการทดสอบนั้นขึ้นอยู่กับทรัพยากรที่ไม่พร้อมใช้งาน
  • โครงสร้างโครงการ — แยก lib/, test/ และ test/helpers/ ออกจากกันเพื่อให้ดูแลรักษาและผสานรวมกับ CI ได้ง่าย

รูปแบบเหล่านี้ช่วยให้โครงการ Bash ของคุณมีวินัยในการทดสอบเช่นเดียวกับที่ใช้กับโครงการซอฟต์แวร์สมัยใหม่ทั่วไป

คำถามที่พบบ่อย

บทเรียน “การทดสอบฟังก์ชันแบบยูนิตด้วย Bats-core” ฟรีหรือไม่

ใช่ — ข้อความเต็มของ “การทดสอบฟังก์ชันแบบยูนิตด้วย Bats-core” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส Linux Command Line & Bash Scripting Mastery ให้อัปเกรดเป็น CoddyKit PRO คอร์ส Linux Command Line & Bash Scripting Mastery มีบทเรียนทั้งหมด 4 บทเรียน

คุณจะเรียนรู้อะไรในบทเรียน “การทดสอบฟังก์ชันแบบยูนิตด้วย Bats-core”

จัดโครงสร้างไฟล์ทดสอบ การตรวจยืนยัน และการตั้งค่า/เก็บกวาด เพื่อยืนยันฟังก์ชัน Bash แต่ละรายการ คุณปฏิบัติ Linux Command Line & Bash Scripting Mastery ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน

คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน Linux Command Line & Bash Scripting Mastery หรือไม่

ไม่จำเป็นต้องมีประสบการณ์มาก่อน Linux Command Line & Bash Scripting Mastery บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 1 จากทั้งหมด 4 บทเรียน

บทเรียน “การทดสอบฟังก์ชันแบบยูนิตด้วย Bats-core” ใช้เวลานานแค่ไหน

บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย

ฉันเขียนและรันโค้ดในบทเรียน Linux Command Line & Bash Scripting Mastery นี้ได้ไหม

ได้ บทเรียน Linux Command Line & Bash Scripting Mastery ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

บทเรียนทั้งหมดในหลักสูตรนี้

  1. การทดสอบฟังก์ชันแบบยูนิตด้วย Bats-core
  2. การจำลองคำสั่งและการทำเครื่องมือภายนอกเป็นสตับ
  3. ฟิกซ์เจอร์ สภาพแวดล้อมชั่วคราว และความครอบคลุม
  4. การเรียกใช้การทดสอบ Shell ในไปป์ไลน์ CI
← กลับไปที่ Linux Command Line & Bash Scripting Mastery