ЁЯПл The SchoolтА║ЁЯз╣ Shell scriptingтА║ЁЯФм рдзрдбрд╛ 12 тАФ Debugging рдЖрдгрд┐ testing: рдкрд╛рд╣рд╛, рд╕рд┐рджреНрдз рдХрд░рд╛
ЁЯЦ╝я╕П See the drawing + lab ЁЯПа Course home ЁЯМ┐ Branch on GitHub тЬПя╕П View source
ЁЯЦ╝я╕П рдЖрдХреГрддреА рдЖрдгрд┐ labThe drawing + lab рдкреВрд░реНрдг рдкрд╛рдирд╛рд╡рд░ рдЙрдШрдбрд╛ тЖЧOpen full page тЖЧ

ЁЯФм рдзрдбрд╛ 12 тАФ Debugging рдЖрдгрд┐ testing: рдкрд╛рд╣рд╛, рд╕рд┐рджреНрдз рдХрд░рд╛

ЁЯУН рддреБрдореНрд╣реА рдЗрдереЗ рдЖрд╣рд╛рдд: 12 рдкреИрдХреА рдзрдбрд╛ 12 ┬╖ ЁЯОУ рд╢реЗрд╡рдЯрдЪрд╛


ЁЯУж рдпрд╛ рдмреНрд░рдБрдЪрдордзреНрдпреЗ рдХрд╛рдп рдЖрд╣реЗ

рдзрдбреЗ 01тАУ12 тАФ рдкреВрд░реНрдг рдХреЛрд░реНрд╕. script рдУрд│-рдУрд│ рдЪрд╛рд▓рддрд╛рдирд╛ рдкрд╛рд╣рдгреЗ, linter, рдЖрдгрд┐ bash рдЪреНрдпрд╛ рд╕реБрдорд╛рд░реЗ рддреАрд╕ рдУрд│реАрдВрдордзрд▓рд╛ test runner тАФ рдЖрдгрд┐ capstone рдореНрд╣рдгреВрди backup.

ЁЯзТ 5 рд╡рд░реНрд╖рд╛рдВрдЪреНрдпрд╛ рдореБрд▓рд╛рд▓рд╛ рд╕рдордЬрд╛рд╡рд▓реНрдпрд╛рд╕рд╛рд░рдЦреЗ

рдХреЗрдЕрд░рдЯреЗрдХрд░ рдХрд╛рд╣реАрддрд░реА рд╡рд┐рдЪрд┐рддреНрд░ рдХрд░рддреЛ рддреЗрд╡реНрд╣рд╛ рддреБрдореНрд╣реА рддреНрдпрд╛рд▓рд╛ рдкреНрд░рддреНрдпреЗрдХ рдЖрджреЗрд╢ рдХрд░рддрд╛рдирд╛ рддреЛ рдореЛрдареНрдпрд╛рдиреЗ рдореНрд╣рдгрд╛рдпрд▓рд╛ рд╕рд╛рдВрдЧрддрд╛ тАФ bash -x. рд░рд╛рддреНрд░реА рдШрдВрдЯрд╛ рдХрд╛рд░реНрдбреЗ рдЪрд╛рд▓рд╡рдгреНрдпрд╛рдЖрдзреА рддреА рдмрд░реЛрдмрд░ рдЖрд╣реЗрдд рдпрд╛рдЪреА рдЦрд╛рддреНрд░реА рдХрд░рд╛рдпрдЪреА рдЕрд╕реЗрд▓, рддрд░ рддреБрдореНрд╣реА рдкреНрд░рддреНрдпреЗрдХрд╛рдЪреА рддрд╛рд▓реАрдо рдШреЗрддрд╛ рдЖрдгрд┐ рддреБрд▓рдирд╛ рдХрд░рддрд╛ тАФ рдЬреЗ рдЭрд╛рд▓реЗ рдЖрдгрд┐ рдЬреЗ рд╡реНрд╣рд╛рдпрд▓рд╛ рд╣рд╡реЗ рд╣реЛрддреЗ рддреНрдпрд╛рдЪреА: рд╣рд╛рдЪ test. bash рдордзрд▓рд╛ test рдореНрд╣рдгрдЬреЗ рдлрдХреНрдд: command рдЪрд╛рд▓рд╡рд╛, рддреНрдпрд╛рдЪреЗ output рдЖрдгрд┐ exit code рдкрд╛рд╣рд╛, pass рдореЛрдЬрд╛. framework рдЖрд╡реГрддреНрддреАрд▓рд╛ bats рдореНрд╣рдгрддрд╛рдд; рдХрд▓реНрдкрдирд╛ рдПрдХрд╛ рдХрд╛рд░реНрдбрд╡рд░ рдорд╛рд╡рддреЗ.

ЁЯЧ║я╕П рдЖрдХреГрддреА

flowchart LR
  T["check 'name' 'wanted' \"$(cmd)\""] --> C{"got contains wanted?"}
  C -->|yes| P["тЬЕ pass++"]
  C -->|no| F["тЭМ fail++ ┬╖ show wanted vs got"]
  P & F --> S["N passed, M failed ┬╖ exit 1 if any failed"]

тЭУ рдХрд╛рдп

ЁЯдФ рдХрд╛

"рдЪрд╛рд▓рдгрд╛рд░реА" script рддрд┐рдЪреНрдпрд╛ рд▓реЗрдЦрдХрд╛рдиреЗ рдлрдХреНрдд рдпрд╢рд╛рдЪреНрдпрд╛ рдорд╛рд░реНрдЧрд╛рд╡рд░ рдЪрд╛рд▓рд╡рд▓реЗрд▓реА рдЕрд╕рддреЗ. рджреЗрдЦрд░реЗрдЦреАрд╢рд┐рд╡рд╛рдп рдЪрд╛рд▓рдгрд╛рд▒реНрдпрд╛ scripts рдирд╛ рдмрд╛рдХреА рд╕рдЧрд│реЗ рдорд╛рд░реНрдЧ рднреЗрдЯрддрд╛рдд. рдЪреМрджрд╛ рдЫреЛрдЯреЗ checks regression рдШрдВрдЯреЗрдЪреНрдпрд╛ рдЖрдзреА рдкрдХрдбрддрд╛рдд.

ЁЯФз рдХрд╕реЗ (рдпрд╛ repo рдордзреНрдпреЗ)

run_tests.sh: check() [[ "$got" == *"$want"* ]] рдиреЗ рддреБрд▓рдирд╛ рдХрд░рддреЗ; exit_code() $? рдкрдХрдбрддреЗ; trap рд╕рд╣ mktemp рдордзреВрди temp рдиреЛрдВрджрд╡рд╣реА; backup tests temp --dest рд╡рд╛рдкрд░рддрд╛рдд, rotation рд╕рд╛рдареА рддреАрдирджрд╛ рдЪрд╛рд▓рддрд╛рдд, рдЖрдгрд┐ рдирдХрд╛рд░ рд╕рд┐рджреНрдз рдХрд░рдгреНрдпрд╛рд╕рд╛рдареА lock рд╣рд╛рддрд╛рдиреЗ рдмрдирд╡рддрд╛рдд; рд╢реЗрд╡рдЯрдЪреА рдУрд│ [[ $fail -eq 0 ]] рд╣рд╛ CI рд╡рд╛рдЪрддреЛ рддреЛ exit code рдЖрд╣реЗ.

ЁЯзк рдХрд░реВрди рдкрд╛рд╣рд╛

bash tests/run_tests.sh
bash -x bin/school.sh list --class 3A 2>&1 | head -12             # every expansion, every command
PS4='+ ${LINENO}: ' bash -x bin/school.sh report 2>&1 | grep '^+' | head -5
command -v shellcheck && shellcheck bin/*.sh lib/*.sh tests/*.sh && echo "shellcheck clean"
# break something on purpose, watch a test catch it:
sed -i.bak 's/valid_class "\$class" || die/true || die/' bin/school.sh; bash tests/run_tests.sh | grep -E 'тЭМ|passed'; mv bin/school.sh.bak bin/school.sh

тЬЕ рддрдкрд╛рд╕рд╛ тАФ рддреБрдореНрд╣рд╛рд▓рд╛ рдХрд╛рдп рджрд┐рд╕рд╛рдпрд▓рд╛ рд╣рд╡реЗ

14 passed, 0 failed рдЖрдгрд┐ exit 0; trace рдордзреНрдпреЗ values рднрд░рд▓реЗрд▓реНрдпрд╛ + local class=, + tail, + grep рдУрд│реА рджрд┐рд╕рддрд╛рдд; рдореБрджреНрджрд╛рдо рдмрд┐рдШрдбрд╡рд▓реНрдпрд╛рд╡рд░ рдиреЗрдордХрд╛ рдПрдХрдЪ test fail рд╣реЛрддреЛ (enrol rejects a bad class) рдЖрдгрд┐ рд╕рд╛рд░рд╛рдВрд╢ 13 passed, 1 failed рд╕рд╛рдВрдЧрддреЛ; file рдкреВрд░реНрд╡рд╡рдд рдХреЗрд▓реНрдпрд╛рд╡рд░ рдкреБрдиреНрд╣рд╛ рд╣рд┐рд░рд╡реЗ рд╣реЛрддреЗ.

ЁЯПБ рддреБрдореНрд╣реА рдЖрддреНрддрд╛рдЪ рдХрд╛рдп рд╕рд┐рджреНрдз рдХреЗрд▓реЗ

рддреБрдореНрд╣реА рдХреЛрдгрддреНрдпрд╛рд╣реА script рд▓рд╛ рд╡рд┐рдЪрд╛рд░ рдХрд░рддрд╛рдирд╛ рдкрд╛рд╣реВ рд╢рдХрддрд╛, рддрд┐рд▓рд╛ lint рдХрд░реВ рд╢рдХрддрд╛, рдЖрдгрд┐ рдЕрдкрдпрд╢рд╛рдЪреНрдпрд╛ рдорд╛рд░реНрдЧрд╛рддрд▓рд╛ regression рдкрдХрдбрдгрд╛рд░рд╛ test рд▓рд┐рд╣реВ рд╢рдХрддрд╛ тАФ bash рдкрд▓реАрдХрдбреЗ рдХреЛрдгрддреНрдпрд╛рд╣реА tools рд╢рд┐рд╡рд╛рдп.

тЪая╕П рдиреЗрд╣рдореАрдЪреНрдпрд╛ рдЪреБрдХрд╛

ЁЯПн рдкреНрд░рддреНрдпрдХреНрд╖ рд╡рд╛рдкрд░рд╛рдд рд╣реЗ рдХрд╛ рдорд╣рддреНрддреНрд╡рд╛рдЪреЗ: deploy script рд╕реБрджреНрдзрд╛ code рдЖрд╣реЗ. рддрд┐рд▓рд╛ рдЗрддрд░ рд╕рдЧрд│реНрдпрд╛рдВрд╕рд╛рд░рдЦрд╛ linter, tests рдЖрдгрд┐ CI job рдорд┐рд│рддреЛ, рдирд╛рд╣реАрддрд░ рдкрд╣рд╛рдЯреЗ 3 рд╡рд╛рдЬрддрд╛ page рдпреЗрддреЛ.

ЁЯПЖ Capstone

backup.sh scheduled, locked, rotated, verified рдЖрдгрд┐ tested рдЖрд╣реЗ. рдПрдХ feature рд╕реБрд░реБрд╡рд╛рддреАрдкрд╛рд╕реВрди рд╢реЗрд╡рдЯрдкрд░реНрдпрдВрдд рдЬреЛрдбрд╛: --include data/*.csv (рдПрдХрдЪ рдирд╛рд╣реА, рдкреНрд░рддреНрдпреЗрдХ рдиреЛрдВрджрд╡рд╣реАрдЪрд╛ backup), рдпрд╢рд╛рдЪреНрдпрд╛ рдорд╛рд░реНрдЧрд╛рд╕рд╛рдареА рдПрдХ test рдЖрдгрд┐ рдЪреБрдХреАрдЪреНрдпрд╛ pattern рд╕рд╛рдареА рдПрдХ test рд╕рд╣. рдордЧ рддреЛ cron рдХрд┐рдВрд╡рд╛ CI schedule рдордзреВрди рдПрдХ рдЖрдард╡рдбрд╛ рдЪрд╛рд▓рд╡рд╛ рдЖрдгрд┐ log рд╡рд╛рдЪрд╛.

ЁЯОУ рдХреЛрд░реНрд╕ рдкреВрд░реНрдг рдЭрд╛рд▓рд╛

рдЕрднреНрдпрд╛рд╕ рдпреЛрдЬрдирд╛ рдмрд╛рд░рд╛рд╣реА рдзрдбреНрдпрд╛рдВрд╡рд░ рдЦреВрдг рдХрд░рддреЗ; quiz рддреЗ рддрдкрд╛рд╕рддреЗ; CI/CD рд╢рд╛рд│рд╛ рдпрд╛ scripts рдкреНрд░рддреНрдпреЗрдХ push рд╡рд░ рдЪрд╛рд▓рд╡рддреЗ, рдЖрдгрд┐ School portal рд╡рд░ certificate рдЖрд╣реЗ.

ЁЯФм Lesson 12 тАФ Debugging & testing: see it, prove it

ЁЯУН You are here: Lesson 12 of 12 ┬╖ ЁЯОУ The last one


ЁЯУж What's in this branch

Lessons 01тАУ12 тАФ the whole course. Watching a script run line by line, the linter, and a test runner in about thirty lines of bash тАФ with the backup as the capstone.

ЁЯзТ Explain like I'm 5

When the caretaker does something odd, you ask them to say each order out loud as they do it тАФ bash -x. When you want to be sure the cards are right before the bell rings them at night, you rehearse each one and compare what happened to what should have: a test. A test in bash is just: run the command, look at its output and exit code, count the passes. The framework version is called bats; the idea fits on a card.

ЁЯЧ║я╕П Diagram

flowchart LR
  T["check 'name' 'wanted' \"$(cmd)\""] --> C{"got contains wanted?"}
  C -->|yes| P["тЬЕ pass++"]
  C -->|no| F["тЭМ fail++ ┬╖ show wanted vs got"]
  P & F --> S["N passed, M failed ┬╖ exit 1 if any failed"]

тЭУ What

ЁЯдФ Why

A script that "works" has been run on the happy path by its author. Unattended scripts meet every other path. Fourteen small checks catch the regression before the bell does.

ЁЯФз How (in this repo)

run_tests.sh: check() compares with [[ "$got" == *"$want"* ]]; exit_code() captures $?; a temp register from mktemp with a trap; the backup tests use a temp --dest, run three times for rotation, and create the lock by hand to prove the refusal; the last line [[ $fail -eq 0 ]] is the exit code CI reads.

ЁЯзк Try it

bash tests/run_tests.sh
bash -x bin/school.sh list --class 3A 2>&1 | head -12             # every expansion, every command
PS4='+ ${LINENO}: ' bash -x bin/school.sh report 2>&1 | grep '^+' | head -5
command -v shellcheck && shellcheck bin/*.sh lib/*.sh tests/*.sh && echo "shellcheck clean"
# break something on purpose, watch a test catch it:
sed -i.bak 's/valid_class "\$class" || die/true || die/' bin/school.sh; bash tests/run_tests.sh | grep -E 'тЭМ|passed'; mv bin/school.sh.bak bin/school.sh

тЬЕ Verify тАФ what you should see

14 passed, 0 failed and exit 0; the trace shows + local class=, + tail, + grep lines with values filled in; after the sabotage exactly one test fails (enrol rejects a bad class) and the summary says 13 passed, 1 failed; restoring the file makes it green again.

ЁЯПБ What you just proved

You can watch any script think, lint it, and write a test that catches a regression in a failure path тАФ with no tools beyond bash.

тЪая╕П Common mistakes

ЁЯПн Why this matters in production: the deploy script is code. It gets a linter, tests and a CI job like everything else, or it gets a 3 a.m. page.

ЁЯПЖ Capstone

backup.sh is scheduled, locked, rotated, verified and tested. Add one feature end to end: --include data/*.csv (back up every register, not one), with a test for the happy path and one for a bad pattern. Then run it from cron or a CI schedule for a week and read the log.

ЁЯОУ The course is complete

The study plan ticks the twelve off; the quiz checks them; the CI/CD school runs these scripts on every push, and the School portal has the certificate.

тЖР Previousci and containersFinished! Take the quiz тЖТcheck what stuck

This page is the lesson's README from the lesson-12-debugging-testing branch, shown here so the whole School stays on one site. Code files open on GitHub at the same branch.