การเรียกใช้การทดสอบ Shell ในไปป์ไลน์ CI
เชื่อม ShellCheck และ Bats เข้ากับ GitHub Actions เพื่อให้ทุกการเปลี่ยนแปลงของ Shell ต้องผ่านการตรวจสอบที่สำเร็จ
การเรียกใช้การทดสอบ Shell ในไปป์ไลน์ CI เป็นบทเรียน DevOps Bootcamp ฟรีบน CoddyKit นี่คือบทเรียนที่ 4 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน DevOps Bootcamp และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส DevOps Bootcamp มีบทเรียนทั้งหมด 4 บทเรียน
เหตุใด CI จึงสำคัญต่อสคริปต์ Shell
สคริปต์ Shell ก็คือโค้ด — และเช่นเดียวกับโค้ดทั้งหมด สคริปต์เหล่านี้ควรมีด่านตรวจสอบคุณภาพแบบอัตโนมัติ หากไม่มี CI การพิมพ์ผิดในสคริปต์ปรับใช้ก็อาจไปถึงระบบจริงอย่างเงียบ ๆ และทำให้ระบบหยุดให้บริการตอนตีสาม
pipeline CI ที่รัดกุมสำหรับโครงการ Bash จะบังคับใช้สองสิ่งในทุก pull request:
- การวิเคราะห์แบบสถิต ผ่าน
ShellCheck— ตรวจจับข้อผิดพลาดทางไวยากรณ์ รูปแบบที่ไม่ปลอดภัย และปัญหาความสามารถในการพกพาตาม POSIX ก่อนที่สคริปต์จะถูกรัน - การทดสอบระดับหน่วย/การผสานรวม ผ่าน
Bats(ระบบทดสอบอัตโนมัติของ Bash) — เรียกใช้ฟังก์ชันของคุณและยืนยันพฤติกรรมที่ถูกต้อง
เมื่อใช้ร่วมกัน เครื่องมือทั้งสองจะเป็นตาข่ายนิรภัยที่ทำให้คุณปรับโครงสร้างโค้ดได้อย่างมั่นใจและช่วยให้สมาชิกใหม่เริ่มงานได้เร็วขึ้น บทเรียนนี้จะเชื่อมเครื่องมือทั้งสองเข้ากับ GitHub Actions ซึ่งเป็นแพลตฟอร์ม CI ฟรีที่ใช้กันแพร่หลายที่สุดสำหรับโครงการโอเพนซอร์สและทีมขนาดเล็ก
บทนำ GitHub Actions สำหรับโครงการ Shell
GitHub Actions คือ CI/CD ที่ขับเคลื่อนด้วยเหตุการณ์และมีอยู่ใน GitHub โดยตรง เวิร์กโฟลว์ คือไฟล์ YAML ที่เก็บไว้ใน .github/workflows/ เวิร์กโฟลว์จะทำงานเมื่อเกิดเหตุการณ์ต่าง ๆ (push, pull_request และอื่น ๆ) และเรียกใช้งานบนตัวเรียกใช้ที่โฮสต์ไว้
แนวคิดสำคัญที่คุณจำเป็นต้องรู้:
on:— ตัวกระตุ้น (เช่นpush,pull_request)jobs:— หน่วยงานที่ทำงานขนานกัน โดยแต่ละหน่วยทำงานบน VM ใหม่steps:— คำสั่ง Shell ตามลำดับหรือ แอ็กชัน ที่นำกลับมาใช้ใหม่ได้ภายในงานruns-on:— อิมเมจของตัวเรียกใช้ (เราใช้ubuntu-latest)
ไฟล์เวิร์กโฟลว์ต้องถูก commit ไว้ใน repository โดย GitHub จะตรวจพบไฟล์เหล่านี้โดยอัตโนมัติ — ไม่ต้องตั้งค่าภายนอกเพิ่มเติม
# Minimal skeleton — .github/workflows/ci.yml
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
shell-checks:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: echo "Steps go here"ติดตั้ง ShellCheck ในเวิร์กโฟลว์
ShellCheck ติดตั้งไว้ล่วงหน้าบนตัวเรียกใช้ ubuntu-latest ดังนั้นโดยมากคุณจึงไม่ต้องมีขั้นตอนการติดตั้งเลย อย่างไรก็ตาม รุ่นที่ติดตั้งไว้ล่วงหน้าอาจตามหลังรุ่นล่าสุด เพื่อให้ build ทำซ้ำได้เหมือนเดิม ควรกำหนดรุ่นที่แน่นอน
กลยุทธ์การติดตั้งสองแบบ:
- ใช้ไบนารีที่ติดตั้งไว้ล่วงหน้า — ง่ายที่สุดและเพียงพอสำหรับโครงการส่วนใหญ่
- ติดตั้งรุ่นที่กำหนดแน่นอน ผ่าน tarball รุ่นเผยแพร่อย่างเป็นทางการของ GitHub — รับประกันว่าใช้รุ่นตัวตรวจสอบเดียวกันทั้งในเครื่องและใน CI
ขั้นตอนด้านล่างแสดงแนวทางการกำหนดรุ่นแน่นอน โดยใช้สตริงรุ่นคงที่ที่เก็บเป็นตัวแปรสภาพแวดล้อม ทำให้การอัปเกรดเปลี่ยนเพียงบรรทัดเดียว
# .github/workflows/ci.yml — ShellCheck install step
- name: Install ShellCheck
env:
SC_VERSION: v0.10.0
run: |
curl -sSfL \
"https://github.com/koalaman/shellcheck/releases/download/${SC_VERSION}/shellcheck-${SC_VERSION}.linux.x86_64.tar.xz" \
| tar -xJf - --strip-components=1 -C /usr/local/bin shellcheck-${SC_VERSION}/shellcheck
shellcheck --versionเรียกใช้ ShellCheck กับสคริปต์ทุกไฟล์
หลังการติดตั้ง คุณต้องมีขั้นตอนที่ค้นหาและตรวจสอบสคริปต์ Shell ทั้งหมดใน repository ใช้ find เพื่อค้นหาไฟล์ แล้วส่งต่อให้ shellcheck
ตัวเลือกสำคัญที่ควรรู้:
-e SC2034— ไม่รวมกฎที่ระบุ (ควรใช้อย่างจำกัดและใส่ความคิดเห็นกำกับ)--severity=warning— ทำให้ไม่ผ่านเฉพาะคำเตือนและระดับที่รุนแรงกว่า (ไม่นับคำแนะนำด้านรูปแบบ)-x— ติดตามคำสั่งsourceเพื่อตรวจสอบไฟล์ที่ถูก source มาด้วย
หาก shellcheck พบปัญหาใด ๆ มันจะจบการทำงานด้วยค่าที่ไม่ใช่ศูนย์ ซึ่งจะทำให้ขั้นตอน CI ไม่ผ่านโดยอัตโนมัติ — ไม่ต้องเขียนตรรกะเพิ่มเติม
# .github/workflows/ci.yml — ShellCheck lint step
- name: Lint shell scripts
run: |
# Find all .sh files and files with a bash/sh shebang
mapfile -t scripts < <(
find . -type f -name '*.sh' -not -path './.git/*'
)
if [[ ${#scripts[@]} -eq 0 ]]; then
echo 'No shell scripts found — skipping.'
exit 0
fi
echo "Linting ${#scripts[@]} file(s)..."
shellcheck --severity=warning -x "${scripts[@]}"Bats คืออะไรและทำงานอย่างไร
Bats (ระบบทดสอบอัตโนมัติของ Bash) คือเฟรมเวิร์กการทดสอบสำหรับ Bash ที่สอดคล้องกับ TAP ไฟล์ test แต่ละไฟล์เป็นไฟล์ .bats ที่ประกอบด้วยบล็อก @test
test จะผ่านเมื่อส่วนเนื้อหาจบการทำงานด้วย 0 และไม่ผ่านเมื่อจบด้วยค่าที่ไม่ใช่ศูนย์ Bats มีตัวแปรและฟังก์ชันตัวช่วยให้ใช้:
$status— รหัสการจบการทำงานของคำสั่งrunล่าสุด$output— stdout และ stderr ที่รวมกันของคำสั่งrunล่าสุด$lines— อาร์เรย์ของบรรทัดเอาต์พุตrun <cmd>— เรียกใช้คำสั่งโดยไม่ทำให้ test ไม่ผ่านเมื่อจบด้วยค่าที่ไม่ใช่ศูนย์
ตัวช่วย run มีความสำคัญอย่างยิ่ง — หากไม่มี ตัวคำสั่งที่ล้มเหลวจะยุติ test ก่อนที่คุณจะตรวจสอบ $status ได้
#!/usr/bin/env bats
# tests/greet.bats
setup() {
# Runs before every @test block
source "${BATS_TEST_DIRNAME}/../lib/greet.sh"
}
@test "greet outputs hello with the given name" {
run greet "Alice"
[ "$status" -eq 0 ]
[ "$output" = "Hello, Alice!" ]
}
@test "greet fails when no argument is provided" {
run greet
[ "$status" -eq 1 ]
[[ "$output" == *"Usage"* ]]
}ติดตั้ง Bats-Core ผ่าน Git Submodule
วิธีมาตรฐานในการเพิ่ม Bats ลงในโครงการคือใช้เป็น Git submodule วิธีนี้จะตรึง commit ที่เฉพาะเจาะจง ทำให้รุ่นของตัวเรียกใช้เหมือนกับรุ่นที่ใช้พัฒนาในเครื่อง และไม่ต้องพึ่งตัวจัดการแพ็กเกจ
เรียกใช้คำสั่งเหล่านี้ครั้งเดียวในเครื่อง แล้ว commit ผลลัพธ์:
git submodule add https://github.com/bats-core/bats-core test/batsgit submodule add https://github.com/bats-core/bats-support test/test_helper/bats-supportgit submodule add https://github.com/bats-core/bats-assert test/test_helper/bats-assert
ใน CI ให้กู้คืน submodule ด้วย actions/checkout@v4 และตัวเลือก submodules: recursive ขั้นตอนด้านล่างแสดงการตั้งค่าการ checkout แบบสมบูรณ์
# .github/workflows/ci.yml — checkout with submodules
- name: Checkout repository
uses: actions/checkout@v4
with:
submodules: recursive # restores bats-core + helpersเรียกใช้ Bats Tests ใน CI
เมื่อมี Bats พร้อมใช้งานแล้ว (ผ่าน submodule หรือการติดตั้งแพ็กเกจ) การเรียกใช้ test จะใช้เพียงคำสั่งเดียว ระบุไดเรกทอรีให้ Bats แล้ว Bats จะค้นหาไฟล์ .bats ทุกไฟล์แบบย้อนลงไปในไดเรกทอรีย่อยด้วยตัวเลือก --recursive
ตัวเลือก --formatter tap จะแสดงผลในรูปแบบ TAP (Test Anything Protocol) ซึ่งระบบ CI จำนวนมากสามารถแยกวิเคราะห์เพื่อรายงานผล test ได้ ส่วนตัวจัดรูปแบบ pretty ที่เป็นค่าเริ่มต้นเหมาะกับการอ่านโดยมนุษย์ในบันทึกดิบมากกว่า
ใช้ --timing เพื่อค้นหา test ที่ทำงานช้าตั้งแต่เนิ่น ๆ — test ที่ใช้เวลานานเกิน 5 วินาทีมักบ่งชี้ว่ามีการเรียกเครือข่ายโดยไม่ต้องการหรือไม่มี mock
# .github/workflows/ci.yml — Bats test step
- name: Run Bats tests
run: |
# If installed as a submodule:
./test/bats/bin/bats \
--recursive \
--timing \
tests/
# If installed via apt or brew (alternative):
# bats --recursive --timing tests/เวิร์กโฟลว์สมบูรณ์: ShellCheck + Bats
ตอนนี้ให้นำทุกอย่างมารวมเป็นไฟล์เวิร์กโฟลว์ไฟล์เดียวที่พร้อมใช้จริง แนวปฏิบัติที่ดีที่สุดที่นำมาใช้มีดังนี้:
- งานที่แยกจากกันสองงาน (
lintและtest) ทำงานขนานกัน ทำให้ได้รับผลตอบกลับเร็วขึ้น - งาน
testระบุneeds: lintดังนั้น test จะทำงานหลังจากการตรวจสอบผ่านแล้วเท่านั้น — ช่วยไม่ให้เสียเวลาของตัวเรียกใช้ไปกับโค้ดที่ผิดพลาดอย่างเห็นได้ชัด - รุ่นของแอ็กชันที่กำหนดแน่นอน (
@v4) ป้องกันความเสียหายที่ไม่คาดคิดจากการอัปเดตต้นทาง - บล็อก
permissions:จำกัดโทเคนของเวิร์กโฟลว์ให้มีสิทธิ์เท่าที่จำเป็น
# .github/workflows/ci.yml
name: Shell CI
on:
push:
branches: [main]
pull_request:
permissions:
contents: read
jobs:
lint:
name: ShellCheck
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run ShellCheck
run: |
mapfile -t scripts < <(find . -name '*.sh' -not -path './.git/*')
[[ ${#scripts[@]} -gt 0 ]] && shellcheck --severity=warning -x "${scripts[@]}"
test:
name: Bats Tests
runs-on: ubuntu-latest
needs: lint
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Run tests
run: ./test/bats/bin/bats --recursive --timing tests/แคช Dependencies เพื่อให้การเรียกใช้เร็วขึ้น
เมื่อมีการติดตั้งตัวช่วยของ Bats หรือเครื่องมืออื่นผ่านตัวจัดการแพ็กเกจภายในเวิร์กโฟลว์ การแคชจะช่วยเร่งการเรียกใช้ครั้งถัดไปได้อย่างมาก GitHub Actions มีแอ็กชัน actions/cache สำหรับงานนี้
ประเด็นสำคัญของการแคชอย่างมีประสิทธิภาพ:
- ใช้คีย์แคชที่มี OS ชื่อเครื่องมือ และแฮชของไฟล์ล็อก — เพื่อให้แคชหมดอายุโดยอัตโนมัติเมื่อ dependencies เปลี่ยนแปลง
- ทางเลือก
restore-keysช่วยให้เวิร์กโฟลว์ใช้แคชเก่าแทนการเริ่มใหม่ทั้งหมดเมื่อไม่พบแคชที่ตรงกัน - สำหรับ Git submodule แทบไม่จำเป็นต้องแคช เพราะการ checkout submodule ทำได้รวดเร็ว แคชมีประโยชน์สูงสุดกับการติดตั้ง
npm,pipหรือเครื่องมือแบบคอมไพล์
# .github/workflows/ci.yml — cache step example
- name: Cache Bats npm helpers
uses: actions/cache@v4
with:
path: ~/.npm
key: ${{ runner.os }}-bats-${{ hashFiles('package-lock.json') }}
restore-keys: |
${{ runner.os }}-bats-
- name: Install helpers
run: npm ci # uses cache when availableการป้องกันสาขา: บังคับใช้การตรวจสอบที่ผ่านทั้งหมด
เวิร์กโฟลว์ CI ที่ไม่บล็อกการผสาน อย่างมากก็เป็นเพียงคำแนะนำ กฎการป้องกันสาขาของ GitHub จะเปลี่ยนการตรวจสอบของคุณให้เป็นด่านบังคับ
วิธีตั้งค่า: ไปที่ Settings → Branches → Add rule สำหรับ main แล้วเปิดใช้:
- Require status checks to pass before merging — เลือก ShellCheck และ Bats Tests ตามชื่อ
- Require branches to be up to date before merging — ป้องกันไม่ให้ PR ที่ผ่านการตรวจสอบบนฐานโค้ดเก่า นำโค้ดที่เสียเข้าสู่สาขาหลัก
- Do not allow bypassing the above settings — บังคับใช้กฎแม้กับผู้ดูแล repository
เมื่อมีกฎเหล่านี้แล้ว เส้นทางเดียวที่จะผสานได้คือ PR ที่งาน CI ทั้งหมดผ่าน — ซึ่งเป็นตาข่ายนิรภัยที่คุณต้องการอย่างแท้จริง
แก้ไขข้อผิดพลาดของขั้นตอน CI ในเครื่อง
เมื่อการเรียกใช้ CI ล้มเหลว วงจรการแก้ไขที่เร็วที่สุดคือจำลองข้อผิดพลาดในเครื่องก่อน push commit อื่น เทคนิคมีสองแบบ:
- เรียกใช้คำสั่งเดียวกันทุกประการจากขั้นตอนที่ล้มเหลวในเทอร์มินัล — CI เรียกใช้ Shell ธรรมดา ดังนั้นคำสั่งจึงคัดลอกไปเรียกใช้ซ้ำได้
- ใช้
act— เครื่องมือที่เรียกใช้เวิร์กโฟลว์ GitHub Actions ในเครื่องภายใน Docker ทำให้สภาพแวดล้อมใกล้เคียงกับตัวเรียกใช้ที่โฮสต์ไว้มากที่สุด
สาเหตุทั่วไปของข้อผิดพลาดที่เกิดเฉพาะใน CI คือรุ่นของเครื่องมือไม่ตรงกันระหว่าง Mac ของคุณ (เช่น find แบบ BSD บน macOS กับ find แบบ GNU บน Ubuntu) ควรทดสอบด้วยตัวเลือก --posix เสมอ หรือใช้ act เพื่อเรียกใช้อิมเมจ Ubuntu ในเครื่อง
#!/usr/bin/env bash
# run_ci_locally.sh — mimic the CI lint step on your machine
set -euo pipefail
echo '=== ShellCheck ==='
mapfile -t scripts < <(find . -name '*.sh' -not -path './.git/*')
if [[ ${#scripts[@]} -eq 0 ]]; then
echo 'No .sh files found.'
else
shellcheck --severity=warning -x "${scripts[@]}"
echo "Linted ${#scripts[@]} file(s) — OK"
fi
echo '=== Bats ==='
./test/bats/bin/bats --recursive --timing tests/ตรวจสอบความรู้: แนวคิด Pipeline ของ CI
ทดสอบความเข้าใจของคุณเกี่ยวกับการเชื่อม ShellCheck และ Bats เข้ากับ GitHub Actions
ทบทวน: CI ของ Shell ด้วย ShellCheck และ Bats
ในบทเรียนนี้ คุณได้สร้าง pipeline CI ที่สมบูรณ์สำหรับโครงการ Bash โดยใช้ GitHub Actions สิ่งที่ครอบคลุมมีดังนี้:
- พื้นฐาน GitHub Actions — YAML ของเวิร์กโฟลว์อยู่ใน
.github/workflows/ทำงานเมื่อเกิด push และ pull_request และเรียกใช้งานบนตัวเรียกใช้ubuntu-latest - ShellCheck — ติดตั้งไว้ล่วงหน้าบนตัวเรียกใช้ Ubuntu ใช้
findเพื่อค้นหาสคริปต์ และใช้--severity=warning -xเป็นด่านตรวจสอบโค้ดที่เหมาะกับการใช้งานจริง - Bats ผ่าน submodule — ตรึง bats-core และตัวช่วยเป็น Git submodule แล้วกู้คืนใน CI ด้วย
submodules: recursiveในแอ็กชัน checkout - ลำดับงาน — ใช้
needs:เพื่อให้ test ทำงานหลังการตรวจสอบผ่านแล้วเท่านั้น ช่วยให้ได้รับผลตอบกลับเร็วและไม่เสียทรัพยากรการประมวลผลโดยเปล่าประโยชน์ - การป้องกันสาขา — บังคับใช้การตรวจสอบสถานะในการตั้งค่า GitHub เพื่อไม่ให้ PR ใดเข้าสู่สาขาหลักโดยไม่มี CI ที่ผ่านทั้งหมด
- การจำลองในเครื่อง — คัดลอกคำสั่ง CI ไปยังเทอร์มินัลโดยตรง หรือใช้
actเพื่อแก้ไขข้อผิดพลาดโดยไม่ต้องสร้าง commit เพิ่ม
เมื่อมี pipeline นี้แล้ว การเปลี่ยนแปลง Shell ทุกครั้งจะได้รับการตรวจสอบโดยอัตโนมัติก่อนแตะต้องสาขาหลักของคุณ
คำถามที่พบบ่อย
บทเรียน “การเรียกใช้การทดสอบ Shell ในไปป์ไลน์ CI” ฟรีหรือไม่
ใช่ — ข้อความเต็มของ “การเรียกใช้การทดสอบ Shell ในไปป์ไลน์ CI” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส DevOps Bootcamp ให้อัปเกรดเป็น CoddyKit PRO คอร์ส DevOps Bootcamp มีบทเรียนทั้งหมด 4 บทเรียน
คุณจะเรียนรู้อะไรในบทเรียน “การเรียกใช้การทดสอบ Shell ในไปป์ไลน์ CI”
เชื่อม ShellCheck และ Bats เข้ากับ GitHub Actions เพื่อให้ทุกการเปลี่ยนแปลงของ Shell ต้องผ่านการตรวจสอบที่สำเร็จ คุณปฏิบัติ DevOps Bootcamp ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน
คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน DevOps Bootcamp หรือไม่
ไม่จำเป็นต้องมีประสบการณ์มาก่อน DevOps Bootcamp บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 4 จากทั้งหมด 4 บทเรียน
บทเรียน “การเรียกใช้การทดสอบ Shell ในไปป์ไลน์ CI” ใช้เวลานานแค่ไหน
บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย
ฉันเขียนและรันโค้ดในบทเรียน DevOps Bootcamp นี้ได้ไหม
ได้ บทเรียน DevOps Bootcamp ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ
บทเรียนทั้งหมดในหลักสูตรนี้
- การทดสอบฟังก์ชันแบบยูนิตด้วย Bats-core
- การจำลองคำสั่งและการทำเครื่องมือภายนอกเป็นสตับ
- ฟิกซ์เจอร์ สภาพแวดล้อมชั่วคราว และความครอบคลุม
- การเรียกใช้การทดสอบ Shell ในไปป์ไลน์ CI