Test Driven Development

na míru snado a rychle

Lukáš Bařinka
LinuxDays 2026

Obsah přednášky

  1. Minimum teorie
  2. Co testovat
  3. Jak testovat
  4. Jak simulovat
  5. Typické problémy
  6. Testovací framework
  7. Typické situace (workshop)

Minimum teorie

Minimum teorie

  1. Co je TDD
  2. Proč TDD
  3. Proč ne TDD
  4. Nebezpečí
  5. Způsoby použití

Co je TDD

Základní cyklus TDD
  • Nejdříve testy, pak kód
  • Základní cyklus:
    Red — Green — Refactor
    1. Napsat testy,
      kód testy neprochází
    2. Napsat minimum kódu,
      kód testy prochází
    3. Vylepšit kód,
      kód testy stále prochází

Proč používat TDD

  • Jistota při změnách
  • Kód jen podle potřeby
  • Testy jako dokumentace
  • Rychlejší vývoj a včasné/levnější odhalení chyb
  • Lepší návrh kódu — menší/testovatelné části
  • Lepší spolupráce v týmu

Proč nepoužívat TDD

  • Zpomaluje dosažení cíle
  • Zvětšuje množství kódu, ztěžuje úpravy
  • Vyžaduje disciplínu
  • Potřebuje vytvoření testovacího prostředí

Problém vytváření testů při
nejasném nebo měnícím se zadání

Nebezpečí

Testování je jen tak dobré, jak dobře jsou navržené samotné testované situace
⇒ nebezpečí falešného bezpečí/sebevědomí

Některé situace se špatně testují, např.:

  • chování uživatele (UI)
  • souběh událostí (race-condition)
  • propustnost/spolehlivost (síťových) služeb

Způsoby práce

  • Zevnitř ven (inside out)
    • testuje se od nejmenších jednotek po větší celky
    • snadnější pro začátečníky
    • omezuje nutnost používat mocks (imitace)
  • Zvenku dovnitř (outside in)
    • testy od celkového chování k detailům
    • silně používá mocks (imitace)
    • lépe odpovídá bussiness/user potřebám

Co testovat

Co testovat

V=foo cmd1 p1 p2 … | V=bar cmd2 … Schéma spolupracujících procesů
  • Návratový kód
  • Výstup/chyby
  • Proměnné
  • Parametry
  • Filesystem
  • Procesy
  • Síť

Návratový kód (exit code)

(exit 1) | (exit 2) | (exit 0)
getent passwd user | grep /bash$
getent passwd user | grep /bash$ | cut -d: -f7
  • $? — návratový kód kolony
    echo $? 0
  • $PIPESTATUS — pole návratových hodnot
    declare -a PIPESTATUS=([0]="1" [1]="2" [2]="0")

Návratový kód (exit code)

(exit 1) | (exit 2) | (exit 0)
getent passwd user | grep /bash$
getent passwd user | grep /bash$ | cut -d: -f7
  • Pipefail — poslední (nenulový) návratový kód
    set -o pipefail; !! ; echo $? 2
  • Subshell — vnější návratový kód (mimo přiřazení)
    echo $(exit 2); echo $? 0
    var=$(exit 2); echo $? 2

Výstup

  • stdout — standardní výstup (fd:1)
  • stderr — standardní chybový výstup (fd:2)
cmd >out 2>err
cmd >out 2>&1 
  • Oříznutí odřádkování u expanze subshellu! out=$( cmd )
  • Binární data (\0) !

Výstup — subshell

out=$( printf '%s\n\n' a b c )
declare -- out=$'a\n\nb\n\nc'
out=$( printf '%s\n\n' a b c; echo . )
declare -- out=$'a\n\nb\n\nc\n\n.'
out=${out%.}
declare -- out=$'a\n\nb\n\nc\n\n'

Výstup — binární data

printf -v out 'a\0b\n\n'
declare -- out="a"
out=$( printf 'a\0b\n\n' | base64 )
declare -- out="YQBiCgo="
base64 -d <<<"$out" | od -c
0000000 a \0 b \n \n
0000005

Proměnné

  • Existence proměnné
    declare -p FOO
    -bash: declare: FOO: not found
    test -v FOO; echo $?
    1
  • Hotnota proměnné
    printf '%s | %q' "$IFS" "$IFS" "${IFS@Q}"

    | $' \t\n' | $' \t\n'
  • Atributy proměnné
    echo "${BASH_VERSINFO@a}"
    ar
declare -ar BASH_VERSINFO=([0]="5" [1]="3" [2]="9" [3]="1" [4]="release" [5]="x86_64-pc-linux-gnu")

Parametry

  • Počet parametrů: $#
  • Jednotlivé parametry: $n, ${n}
    • $0 — jméno skriptu
    • $1 … $9 — parametry
    • ${10} … — parametry
  • Posun parametrů: shift n

Filesystem

  • Metadata souborů: stat
  • Obsah souborů: cat, od, strings
  • Adresářová struktura: find

Filesystem

  • Metadata souborů: stat
  • stat -c "%s" /etc/issue
    34
  • Obsah souborů: cat, od, strings
  • Adresářová struktura: find

Filesystem

  • Metadata souborů: stat
  • Obsah souborů: cat, od, strings
  • cat -A /etc/issue
    Debian GNU/Linux forky/sid \n \l$
    $
    od -c /etc/issue
    0000000 D e b i a n G N U / L i n u x
    0000020 f o r k y / s i d \ n \ l
    0000040 \n \n
    0000042
    strings /usr/lib/x86_64-linux-gnu/libm.so
    /* GNU ld script
    OUTPUT_FORMAT(elf64-x86-64)
    GROUP ( /usr/lib/x86_64-linux-gnu/libm.so.6 AS_NEEDED ( /usr/lib/x86_64-linux-gnu/libmvec.so.1 ) )
  • Adresářová struktura: find

Filesystem

  • Metadata souborů: stat
  • Obsah souborů: cat, od, strings
  • Adresářová struktura: find
  • find cesty… predikáty…
    find / \
    \( \
    \( -path /home -o -path /run -o -path /mnt \) \
    -prune \
    \) -o \( \
    \! -type l \
    -size +0 \
    -writable \
    -printf '%y %m %s\t%p\n' \
    \) 2>/dev/null
    d 3775 4096 /usr/share/ppd/custom
    d 1777 4096 /var/tmp
    d 755 4096 /var/tmp/flatpak-cache-B2V7U3
    d 1777 9700 /tmp
    f 600 49478 /tmp/.com.google.Chrome.k8zck4
    f 664 68 /tmp/sum
    f 664 15 /tmp/f2
    f 664 12 /tmp/f1

Procesy a síť

  • Procesy: ps, pgrep, lsof
  • Síť: ping, ss, netstat, nc, curl
  • Timeout: timeout

Procesy a síť

  • Procesy: ps, pgrep, lsof
  • Síť: ping, ss, netstat, nc, curl
  • Timeout: timeout
  • timeout 5 bash -c 'while sleep 1; do date +%T; done'
    13:15:01
    13:15:02
    13:15:03
    13:15:04

Jak testovat

Jak testovat

  • Parametry a vstupy
  • Vestavěné testy v shellu
  • Externí testy

Předávání parametrů

cmd -o -O optarg arg1 arg2 arg3
$0  $1 $2   $3    $4   $5   $6   $#=6

Předávání parametrů

cmd -o -O optarg arg1 arg2 arg3
$0  $1 $2   $3    $4   $5   $6   $#=6
  • Nastavení uvnitř skriptu
    set -- -o -O optarg arg1 arg2 arg3
  • Nastavení vně skriptu
    params=( -o -O optarg arg1 arg2 arg3 )
    cmd "${params[@]}"

Předávání vstupu

  • Ze souboru
  • Here-document
  • Here-string

Předávání vstupu

  • Ze souboru
  • cmd <file
  • Here-document
  • cmd <<HERE
    $PWD
    $(date +%Y)
    \\
    HERE

    /tmp
    2026
    \

    cmd <<'HERE'
    $PWD
    $(date +%Y)
    \\
    HERE

    $PWD
    $(date +%Y)
    \\

  • Here-string
  • cmd <<<"one string"

Vestavěné testy

  • Příkaz test a [
  • Prostředí logických výrazů [[ expr ]]
  • Prostředí aritmetických výrazů (( expr ))

Vestavěné testy

  • Příkaz test a [
    • Aritmetika: -lt -gt -eq …
    • Řetězce: -z -n = != < >
    • Soubory: -e -f -s -d -t -r -O …
    • Logické operátory, proměnné, nastavení shellu:
      ! -a -o -v -o …
    test -f file -a \( -O file -o -w file \) [ -f file -a \( -O file -o -w file \) ]
  • Prostředí logických výrazů [[ expr ]]
  • Prostředí aritmetických výrazů (( expr ))

Vestavěné testy

  • Příkaz test a [
  • Prostředí logických výrazů [[ expr ]]
    • SP: ==
    • ERE: =~
    • Logické operátory: && ||
    [[ -f file && ( -O file || -w file ) ]] [[ $var == foo* || $var =~ [0-9]+ ]]
  • Prostředí aritmetických výrazů (( expr ))

Vestavěné testy

  • Příkaz test a [
  • Prostředí logických výrazů [[ expr ]]
  • Prostředí aritmetických výrazů (( expr ))
    • Celočíselná aritmetika
    • Libovolný základ (dec/0/0x/base#…)
    • Bez kontroly přetečení
    i=009; (( i=10#$i, j--, k=i+j, k>0 ))

Externí testy

  • Logické výrazy
    /bin/test a /bin/[
  • Regulární výrazy
    grep
  • Porovnání obsahu
    cmp, diff, comm, sha*sum

Testy regulárním výrazem

-G   BRE   -E ERE   -P perl   -F fixed
grep "$RE"
grep -e "$RE1" -e "$RE2"
grep -f regex_file
  • -icase insensitive
  • -v,-w,-xvice-versa, word/exact match
  • -q,-squiet, silent
  • -m 1max count

Porovnání obsahu

  • sha*sum test kontrolního součtu
  • cmp binární porovnávání obsahu
  • comm společné řádky (množinové porovnání)
  • diff rozdíly/změny dvou verzí

Porovnání obsahu

  • sha*sum test kontrolního součtu
  • sha256sum f1 f2
    e3b0c44298fc1…495991b7852b855 f1
    e3b0c44298fc1…495991b7852b855 f2
    cmd | sha256sum >sum
    cmd | sha256sum -c --status sum
  • cmp binární porovnávání obsahu
  • comm společné řádky (množinové porovnání)
  • diff rozdíly/změny dvou verzí

Porovnání obsahu

  • sha*sum test kontrolního součtu
  • cmp binární porovnávání obsahu
  • cmp f1 f2
    f1 f2 differ: char 11, line 1
    cmd | cmp -s - file
  • comm společné řádky (množinové porovnání)
  • diff rozdíly/změny dvou verzí

Porovnání obsahu

  • sha*sum test kontrolního součtu
  • cmp binární porovnávání obsahu
  • comm společné řádky (množinové porovnání)
  • printf '%s\n' a b c d e f >f1
    printf '%s\n' a bb c e ee gg >f2
    comm f1 f2
    a
    b
    bb
    c
    d
    e
    ee
    f
    gg
    comm -12 f1 f2
    a
    c
    e
  • diff rozdíly/změny dvou verzí

Porovnání obsahu

  • sha*sum test kontrolního součtu
  • cmp binární porovnávání obsahu
  • comm společné řádky (množinové porovnání)
  • diff rozdíly/změny dvou verzí
  • diff f1 f2
    2c2
    < b
    ---
    > bb
    4d3
    < d
    6c5,6
    < f
    ---
    > ee
    > gg

    diff -u f1 f2
    --- f1 …
    +++ f2 …
    @@ -1,6 +1,6 @@
    a
    -b
    +bb
    c
    -d
    e
    -f
    +ee
    +gg

Typické problémy

Typické problémy

  • Navzájem závislé testy
  • Nepořádek a úklid po testech
  • Prolínání testů a vývoje do lokálního systému
  • Nestabilní stavy (race condition, date, síť, …)

Navzájem závislé testy

  • Špatné rozdělení problému na podproblémy
  • Rozdělit test na (menší) nezávislé části

Navzájem závislé testy

  • Špatné rozdělení problému na podproblémy
  • Rozdělit test na (menší) nezávislé části
  • Špatně definované prostředí a chování
  • Původní závislost definovat jako podmínky běhu a ty kontrolovat

Navzájem závislé testy

  • Špatné rozdělení problému na podproblémy
  • Rozdělit test na (menší) nezávislé části
  • Špatně definované prostředí a chování
  • Původní závislost definovat jako podmínky běhu a ty kontrolovat
  • Šetření práce na špatném místě (testování)
  • Komplikuje odhalení chyb (komplexita roste)

Navzájem závislé testy

  • Špatné rozdělení problému na podproblémy
  • Rozdělit test na (menší) nezávislé části
  • Špatně definované prostředí a chování
  • Původní závislost definovat jako podmínky běhu a ty kontrolovat
  • Šetření práce na špatném místě (testování)
  • Komplikuje odhalení chyb (komplexita roste)
  • Obvykle důsledek testování implementace místo implementace, která bude procházet testy

Navzájem závislé testy

mkdir "$BASE"
cd "$BASE"
touch "$LOG_FILE"
[[ -d $BASE ]]
[[ $PWD == $BASE ]]
[[ -f $LOG_FILE ]]

Navzájem závislé testy

[[ -f $BASE/$LOG_FILE ]]
mkdir "$BASE"
cd "$BASE"
touch "$LOG_FILE"
mkdir -p "$BASE"
>> "$BASE/$LOG_FILE"
export BASE='/tmp/readonly'
export LOG_FILE='access.log'

PERM='u-wx'
mkdir "$BASE"; chmod "$PERM" "$BASE"

./script
(( $? != 0 ))

Nepořádek po testech

mkdir "$BASE" && touch "$BASE/$LOG_FILE"
export BASE='logs'
export LOG_FILE='access.log'
./script
(( $? == 0 ))
[[ -f $BASE/$LOG_FILE ]]

✔️ true
✔️ true
export BASE='logs'
LOG_FILE='my special log'
./script
(( $? == 0 ))
[[ -f $BASE/$LOG_FILE ]]

❌ false
❌ false

Úklid po testech

mkdir "$BASE" && touch "$BASE/$LOG_FILE"
TMP=$( mktemp -d )

export BASE=$TMP/logs
export LOG_FILE='access.log'
./script
(( $? == 0 ))
[[ -f $BASE/$LOG_FILE ]]

rm -rf -- "$TMP"

Bezpečné prostředí vývoje a testů

  • Zamezení bezpečnostním incidentům
    (únik/smazání dat, odlišná identita procesů)
  • Oddělení instalace a nastavení nástrojů pro vyvíjený systém od nástrojů lokálního systému
    (replikovatelnost, verze, rozsah práv, konfigurace, …)
  • Snadnější obnova stavu po havárii
    (testování, co se stane, když …)
  • Snadnější dodržení nezávislých testů a nastavení testovaných stavů
    (zahodit testovaný stav a postavit prostředí nanovo)

Kontejnerizace

  • Provoz kontejnerů pomocí docker, podman
  • Definice image kontejneru v Dockerfile (co)
  • Propojení kontejneru s lokálním systémem:
    • Mapování adresářů (v režimu R/W nebo R-O)
    • Mapování portů
    • Mapování uživatelů
    uložené v docker-compose.yml (jak)

Sestavení image kontejneru

podman build -t dev .
  • Výchozí image
  • FROM debian:bookworm-slim
  • Instalace nástrojů
  • RUN apt-get update && apt-get install -y curl jq …
  • Pracovní adresář
  • WORKDIR /workspace
  • Výchozí příkaz po spuštění
  • CMD ["/bin/bash"]

Sestavení image kontejneru

podman build -t dev .
  • FROM debian:bookworm-slim
  • RUN apt-get update && apt-get install -y curl jq …
  • WORKDIR /workspace
  • CMD ["/bin/bash"]

Image s běžným uživatelem

podman build -t dev .
  • FROM debian:bookworm-slim
  • RUN apt-get update && apt-get install -y curl jq …
  • RUN groupadd -g 1000 tester \
    && useradd -u 1000 -g tester -s /bin/bash -m tester
  • WORKDIR /workspace
  • RUN chown -R tester:tester /workspace
  • USER tester
  • CMD ["/bin/bash"]

Image s běžným uživatelem

podman build -t dev .
  • FROM debian:bookworm-slim
  • RUN apt-get update && apt-get install -y curl jq …
  • ARG U=1000
  • ARG G=1000
  • RUN groupadd -g $G tester \
    && useradd -u $U -g tester -s /bin/bash -m tester
  • WORKDIR /workspace
  • RUN chown -R tester:tester /workspace
  • USER tester
  • CMD ["/bin/bash"]
podman build \
--build-arg U=$(id -u) \
--build-arg G=$(id -g) \
-t dev .

Spuštění kontejneru

podman run -it --rm dev
  • Namapování lokálního uživatele na kontejnerového
    --userns=keep-id
  • Připojení aktuálního adresáře v režimu read/write
    -v "$PWD:/workspace"
  • Připojení aktuálního adresáře v režimu overlay
    -v "$PWD:/workspace:O"

Spuštění programu v kontejneru

podman run -it --rm dev env
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:…
container=podman
TERM=xterm
HOME=/home/tester
HOSTNAME=3898f4a36c14
podman run -it --rm \
--userns=keep-id \
-v "$PWD:/workspace:O" \
dev ./script

Compose

  • Definice kontejneru v docker-compose.yml
  • services:
    dev:
    build: .
    image: dev
    userns_mode: "keep-id"
    volumes:
    - .:/workspace:O
  • Sestavení: podman-compose build
  • podman-compose build --build-arg U=$(id -u) --build-arg G=$(id -g)
  • Spouštění: podman-compose run --rm dev

Nestabilní stavy

  • Souběh (race condition) — snažit se omezit
    • serializací úloh / testů
    • použitím zámků (! test&set)
    • definicí závislostí
    • v nejhorším případě pomocí sleep n
  • Časová závislost — stabilizovat čas (parametrizovat)
    • zjistit aktuální čas pouze jednou
    • TS=$( date +%s ); date -d @$TS
    • nastavit čas podle parametru
  • Síť, služby, …
    • používat lokální instance/mock
    • omezit čas běhu pomocí timeout

Testovací framework

Testovací framework

  • Vlastní
  • Podle konkrétní technologie
    (např. bats pro shell)

Vlastní testovací framework

  • Minimalistický (testuje nezbytné minimum)
  • Rychlé nasazení
  • Žádná/minimální syntaxe/konfigurace
  • Žádná/minimální konfigurovatelnost
  • Vlastní práce
  • S přibývajícími možnostmi roste komplexita

Vlastní testovací framework

  • test-1
  • #!/usr/bin/env bash
  • export BASE=logs
  • export LOG_FILE=access.log
  • ./script
podman-compose run --rm dev ./test-1
  • test-2 #!/usr/bin/env bash
  • export BASE=/logs
  • export LOG_FILE=access.log
  • ./script
podman-compose run --rm dev ./test-2
mkdir: cannot create directory '/logs': Permission denied

Vlastní testovací framework

  • #!/usr/bin/env bash
  • export BASE=logs
  • export LOG_FILE=access.log
  • if ./script >/tmp/out 2>/tmp/err; then
  • echo "$0: OK"
  • else echo "$0: FAILED"
  • fi
podman-compose run --rm dev ./test-1
./test-1: OK
podman-compose run --rm dev ./test-2
./test-2: FAILED

Vlastní testovací framework

  • #!/usr/bin/env bash
  • export BASE=logs
  • export LOG_FILE=access.log
  • exp_EC=0
  • ./script >/tmp/out 2>/tmp/err
  • EC=$?
  • r=FAILED
  • (( EC == exp_EC )) && r=OK
  • echo "${0##*/}: $r"
for t in test-*; do podman-compose run --rm dev ./$t; done
test-1: OK
test-2: OK

Vlastní testovací framework

  • #!/usr/bin/env bash
  • export BASE=logs
  • export LOG_FILE=access.log
  • exp_EC=0
  • exp_file=0
  • ./script >/tmp/out 2>/tmp/err
  • EC=$?
  • r=FAILED
  • d='exit code'
  • (( EC == exp_EC )) && r=OK
  • echo "${0##*/}: $d: $r"
  • r=FAILED
  • d='log file'
  • [[ -f $BASE/$LOG_FILE ]]
  • (( $? == exp_file )) && r=OK
  • echo "${0##*/}: $d: $r"
!!
test-1: exit code: OK
test-1: log file: OK
test-2: exit code: OK
test-2: log file: OK

Vlastní testovací framework

  • #!/usr/bin/env bash
  • export BASE=/logs
  • export LOG_FILE=access.log
  • exp_EC=1
  • exp_file=1
  • cat >/tmp/exp_err <<ERR
  • mkdir: cannot create directory '$BASE': Permission denied
  • ERR
  • ./script >/tmp/out 2>/tmp/err
  • EC=$?
  • ...
  • r=FAILED
  • d='error output'
  • cmp -s /tmp/err /tmp/exp_err && r=OK
  • echo "${0##*/}: $d: $r"

Vlastní testovací framework

  • #!/usr/bin/env bash
  • export BASE=/logs
  • export LOG_FILE=access.log
  • exp_EC=1
  • exp_file=1
  • cat >/tmp/exp_err <<ERR
  • mkdir: cannot create directory '$BASE': Permission denied
  • ERR
  • res() { printf '%s: %-15s %s\n' "${0##*/}" "$d:" "$r"; }
  • equals() { r=❌; (($1==$2)) && r=✔️; res; }
  • is_file() { r=❌; [[ -f $1 ]]; (($?==$2)) && r=✔️; res; }
  • compare() { r=❌; cmp -s "$1" "$2" && r=✔️; res; }
  • ./script >/tmp/out 2>/tmp/err
  • EC=$?
  • d='exit code' ; equals "$EC" "$exp_EC"
  • d='log file' ; is_file "$BASE/$LOG_FILE" "$exp_file"
  • d='error output'; compare /tmp/err /tmp/exp_err

Vlastní testovací framework

test.lib
  • res() { printf '%s: %-15s %s\n' "${0##*/}" "$d:" "$r"; }
  • equals() { r=❌; (($1==$2)) && r=✔️; res; }
  • is_file() { r=❌; [[ -f $1 ]]; (($?==$2)) && r=✔️; res; }
  • compare() { r=❌; cmp -s "$1" "$2" && r=✔️; res; }
  • printf '%s: %s\n' "${0##*/}" "$desc"
  • "$script_file" >/tmp/out 2>/tmp/err
  • EC=$?
  • d='exit code' ; equals "$EC" "$exp_EC"
  • d='log file' ; is_file "$BASE/$LOG_FILE" "$exp_file"
  • d='error output'; compare /tmp/err /tmp/exp_err

Vlastní testovací framework

test-2
  • #!/usr/bin/env bash
  • script_file=./script
  • desc='Read-only directory'
  • export BASE=/test-dir
  • export LOG_FILE=access.log
  • exp_EC=1
  • exp_file=1
  • cat >/tmp/exp_err <<ERR
  • mkdir: cannot create directory '$BASE': Permission denied
  • ERR
  • source ./test.lib

Vlastní testovací framework

  • for t in test-*; do podman-compose run --rm dev ./$t; done
  • test-1: Basic test
  • test-1: exit code: ✔️
  • test-1: log file: ✔️
  • test-1: error output: ✔️
  • test-2: Read-only directory
  • test-2: exit code: ✔️
  • test-2: log file: ✔️
  • test-2: error output: ✔️

Existující framework

  • Minimální práce
  • Údržba a rozvoj frameworku
  • Nutnost naučit se syntaxi/konfiguraci
  • Omezené možnosti frameworku
  • Přizpůsobení se filozofii nástroje

Existující framework (bats)

  • #!/usr/bin/env bats
  • bats_require_minimum_version 1.5.0
  • load '/usr/lib/bats/bats-support/load'
  • load '/usr/lib/bats/bats-assert/load'
  • @test "Accessible BASE dir" {
  • export BASE=test-dir
  • export LOG_FILE=access.log
  • run --separate-stderr ./script
  • assert_success
  • assert_equal "$stderr" ""
  • [[ -f $BASE/$LOG_FILE ]]
  • }
  • @test "Inaccessible BASE dir" {
  • export BASE=/test-dir
  • export LOG_FILE=access.log
  • run --separate-stderr ./script
  • assert_failure
  • assert_equal "$stderr" "mkdir: cannot create directory '$BASE': Permission denied"
  • [[ ! -f $BASE/$LOG_FILE ]]
  • }
./test.bats
test.bats
✓ Accessible BASE dir
✓ Inaccessible BASE dir

2 tests, 0 failures

Mock (imitace)

Mock (imitace)

  • Pokud je potřeba zatím neexistující
    • Alias / funkce
    • Program / skript
    • Služba / rozhraní
  • Pokud má jejich použití nežádoucí efekty

Mock (imitace)

  • Alias / funkce
  • Program / skript
  • Služba / rozhraní

Mock (imitace)

  • Alias / funkce
  • alias get_data='echo "{\"id\":0,\"val\":\"mock\"}"'
    get_data | jq .
    {
    "id": 0,
    "val": "mock"
    }
    get_data | jq -r .val
    mock
    get_data() { echo '{"id":0,"val":"mock"}'; }'
    get_data | jq -r .val
    mock
  • Program / skript
  • Služba / rozhraní

Mock (imitace)

  • Alias / funkce
  • Program / skript
  • get_data #!/usr/bin/env bash
    cat <<'DATA'
    {
    "id":0,
    "val":"mock"
    }
    DATA
    chmod +x get_data
    ./get_data | jq -r .val
    mock
    alias get_data=~+/get_data
    get_data | jq -r .val
    mock
  • Služba / rozhraní

Mock (imitace)

  • Alias / funkce
  • Program / skript
  • Služba / rozhraní
  • get_data #!/usr/bin/env bash
    cat <<'DATA'
    Content-Type: application/json

    {
    "id":0,
    "val":"mock"
    }
    DATA
    chmod +x get_data
    mkdir cgi-bin
    mv get_data cgi-bin
    python3 -m http.server --cgi 8888
    curl localhost:8888/cgi-bin/get_data | jq -r .val
    mock

Typické situace v TDD

~ Workshop ~

15:00 — 15:50
Místnost 345
git clone https://gitlab.com/lukasbarinka/tdd.git

Typické situace

  • Základní testy — $?, test, cmp
  • White/black listing — grep, comm, diff
  • Test parametrů ­— framework
  • Test souborů v kontejneru ­— podman

Základní testy 🗁 basic/01 . ps1-ec
🗁 basic/01 . ps1-color

  • Přidat návratový kód do promptu
  • PS1='\w $?↗ '
  • Obarvit prompt podle návratového kódu
  • ~ 0 $ PS1='\[\033[0;$(($?==0?32:31))m\]\w $?↗ \[\033[0m\]'
  • ~ 0 $ false
  • ~ 0 $ true
  • ~ 0 $

Základní testy 🗁 basic/02 ./date
./date2

  • Jak často nevrátí date;date stejné časy?
  • while :; do
  • d1=$(date) d2=$(date)
  • [[ $d1 != $d2 ]] && echo -n !
  • done

Základní testy 🗁 basic/03 ./date
./date2

  • Jak často nevrátí date;date stejné časy?
  • while :; do
  • date >/tmp/d1
  • date >/tmp/d2
  • cmp -s /tmp/d1 /tmp/d2 || echo -n !
  • done

White/black list 🗁 list/00 🗋 data
🗋 white
🗋 black

  • Filtr number vypisující desetinná čísla
  • Všechna testovací data: data
  • Testovací data se správnýmí čísly: white
  • Testovací data s ostatními hodnotami: black

White/black list 🗁 list/01 ./check
./check2
./check3

  • Nástroj comm pracuje nad seřazenými soubory
  • sort data >/tmp/data.s
  • sort white >/tmp/white.s
  • sort black >/tmp/black.s
  • sort white black >/tmp/all.s
  • Seřazený přehled ne/procházejících dat
  • comm /tmp/white.s /tmp/black.s
  • Jsou white a black disjunktní?
  • comm -12 /tmp/white.s /tmp/black.s
  • Nechybí nějaká data?
  • comm -3 /tmp/data.s /tmp/all.s

White/black list 🗁 list/02 🗋 regex
./test

  • Naivní řešení v souboru: regex
  • [0-9]*
  • Test porovnávající filtr s white-listem
  • grep -Ex -f regex data | diff - white
  • Test porovnávající opak filtru s black-listem
  • grep -vEx -f regex data | diff - black
  • Cílem je mít výstup diff prázdný
    (prochází vše, co by mělo a nic, co by nemělo)

White/black list 🗁 list/03 🗋 regex
./test

  • [+-]?[0-9]*+
  • [+-]?[0-9]+*\.[0-9]+

Test parametrů 🗁 args/00 🗋 data

Skript maxint vypíše nejvyšší zadané celé číslo.

Args.StdOutStdErrEC
2 120
2 220
11 9110
9 09090
220
-0-00
2 3 130
1 -110
Args.StdOutStdErrEC
Args2
fooNaN1
1fooNaN1
1 foo1NaN1
1 2.5 33NaN1

Test parametrů 🗁 args/01 🗋 script
🗋 test-01

script

echo '#!/usr/bin/env bash' >script
chmod +x script
./script

test-01

#!/usr/bin/env bash
# args: 2 1
# exp_out: 2

out=$( ./script 2 1 )

r=FAILED
[[ $out == 2 ]] && r=OK
echo "$0: $r"

Test parametrů 🗁 args/01 🗋 script
🗋 test-01

script
  • #!/usr/bin/env bash

  • ./test-01
  • ./test-01: FAILED

Test parametrů 🗁 args/02 🗋 script
🗋 test-01

script
  • #!/usr/bin/env bash
  • printf '%d\n' "$@" | sort | tail -n 1

  • ./test-01
  • ./test-01: OK

Test parametrů 🗁 args/03 🗋 script
🗋 test-01
🗋 test-02
./run

test-02
  • #!/usr/bin/env bash
  • # args: 2 2
  • # exp_out: 2
  • out=$( ./script 2 2 )
  • r=FAILED
  • [[ $out == 2 ]] && r=OK
  • echo "$0: $r"

  • for t in test-*; do ./$t; done
  • ./test-01: OK
  • ./test-02: OK

Test parametrů 🗁 args/04 🗋 script
🗋 test-01
🗋 test-02
🗋 test-03
./run

test-03
  • #!/usr/bin/env bash
  • # args: 11 9
  • # exp_out: 11
  • out=$( ./script 11 9 )
  • r=FAILED
  • [[ $out == 11 ]] && r=OK
  • echo "$0: $r"

  • for t in test-*; do ./$t; done
  • ./test-01: OK
  • ./test-02: OK
  • ./test-02: FAILED

Test parametrů 🗁 args/05 🗋 script
🗋 test-01
🗋 test-02
🗋 test-03
./run

test-03
  • #!/usr/bin/env bash
  • args=( 11 9 )
  • exp_out=11
  • out=$( ./script "${args[@]}" )
  • r=FAILED
  • [[ $out == "$exp_out" ]] && r=OK
  • echo "$0: $r"

  • for t in test-*; do ./$t; done
  • ./test-01: OK
  • ./test-02: OK
  • ./test-02: FAILED

Test parametrů 🗁 args/06 🗋 script
🗋 test
🗋 test-01
🗋 test-02
🗋 test-03
./run

test-03
  • #!/usr/bin/env bash
  • args=( 11 9 )
  • exp_out=11
  • source ./test
test
  • out=$( ./script "${args[@]}" )
  • r=FAILED
  • [[ $out == "$exp_out" ]] && r=OK
  • echo "$0: $r"

  • for t in test-*; do ./$t; done
  • ./test-01: OK
  • ./test-02: OK
  • ./test-02: FAILED

Test parametrů 🗁 args/07 🗋 script
🗋 test
🗋 test-01
🗋 test-02
🗋 test-03
./run

test-03
  • args=( 11 9 )
  • exp_out=11
test
  • #!/usr/bin/env bash
  • source "./$1" || exit 2
  • out=$( ./script "${args[@]}" )
  • r=FAILED
  • [[ $out == "$exp_out" ]] && r=OK
  • echo "$0: $r"

  • for t in test-*; do ./test "$t"; done
  • ./test-01: OK
  • ./test-02: OK
  • ./test-02: FAILED

Test parametrů 🗁 args/08 🗋 data
🗋 script
./test

test
  • #!/usr/bin/env bash
  • IFS='|'
  • sed '/^ *#/d;s/ *|/|/g;s/| */|/g' data \
  • | while read -r a exp_out exp_err exp_EC; do
  • IFS=" "
  • read -ra args <<<"$a"
  • out=$( ./script "${args[@]}" 2>/dev/null )
  • r=FAILED
  • [[ $out == "$exp_out" ]] && r=OK
  • echo "$0: $r"
  • printf '%-10s ⇒ %-5s : %-5s %s\n' \
  • "${args[*]}" ".$exp_out." ".$out." "$r"
  • IFS='|'
  • done
  • ./test
  • 2 1 ⇒ .2. : .2. OK
  • 2 2 ⇒ .2. : .2. OK
  • 11 9 ⇒ .11. : .9. FAILED
  • 9 09 ⇒ .09. : .9. FAILED
  • 2 ⇒ .2. : .2. OK
  • -0 ⇒ .-0. : .0. FAILED
  • 2 3 1 ⇒ .3. : .3. OK
  • 1 -1 ⇒ .1. : .1. OK
  • ⇒ .. : .0. FAILED
  • foo ⇒ .. : .0. FAILED
  • 1fo ⇒ .. : .1. FAILED
  • 1 foo ⇒ .1. : .1. OK
  • 1 2.5 3 ⇒ .3. : .3. OK

Test parametrů 🗁 args/09 🗋 data
🗋 script
./test

script
  • #!/usr/bin/env bash
  • printf '%ds\n' "$@" | sort | tail -n 1
  • ./test
  • 2 1 ⇒ .2. : .2. OK
  • 2 2 ⇒ .2. : .2. OK
  • 11 9 ⇒ .11. : .9. FAILED
  • 9 09 ⇒ .09. : .9. FAILED
  • 2 ⇒ .2. : .2. OK
  • -0 ⇒ .-0. : .0. FAILED
  • 2 3 1 ⇒ .3. : .3. OK
  • 1 -1 ⇒ .1. : .1. OK
  • ⇒ .. : .0. FAILED
  • foo ⇒ .. : .0. FAILED
  • 1fo ⇒ .. : .1. FAILED
  • 1 foo ⇒ .1. : .1. OK
  • 1 2.5 3 ⇒ .3. : .3. OK
  • ./test
  • 2 1 ⇒ .2. : .2. OK
  • 2 2 ⇒ .2. : .2. OK
  • 11 9 ⇒ .11. : .9. FAILED
  • 9 09 ⇒ .09. : .9. FAILED
  • 2 ⇒ .2. : .2. OK
  • -0 ⇒ .-0. : .-0. OK
  • 2 3 1 ⇒ .3. : .3. OK
  • 1 -1 ⇒ .1. : .1. OK
  • ⇒ .. : .. OK
  • foo ⇒ .. : .foo. FAILED
  • 1fo ⇒ .. : .1fo. FAILED
  • 1 foo ⇒ .1. : .1. OK
  • 1 2.5 3 ⇒ .3. : .3. OK

Test parametrů 🗁 args/10 🗋 data
🗋 script
./test

script
  • #!/usr/bin/env bash
  • printf '%s\n' "$@" | sort -n | tail -n 1
  • ./test
  • 2 1 ⇒ .2. : .2. OK
  • 2 2 ⇒ .2. : .2. OK
  • 11 9 ⇒ .11. : .9. FAILED
  • 9 09 ⇒ .09. : .9. FAILED
  • 2 ⇒ .2. : .2. OK
  • -0 ⇒ .-0. : .-0. OK
  • 2 3 1 ⇒ .3. : .3. OK
  • 1 -1 ⇒ .1. : .1. OK
  • ⇒ .. : .. OK
  • foo ⇒ .. : .foo. FAILED
  • 1fo ⇒ .. : .1fo. FAILED
  • 1 foo ⇒ .1. : .1. OK
  • 1 2.5 3 ⇒ .3. : .3. OK
  • ./test
  • 2 1 ⇒ .2. : .2. OK
  • 2 2 ⇒ .2. : .2. OK
  • 11 9 ⇒ .11. : .11. OK
  • 9 09 ⇒ .09. : .9. FAILED
  • 2 ⇒ .2. : .2. OK
  • -0 ⇒ .-0. : .-0. OK
  • 2 3 1 ⇒ .3. : .3. OK
  • 1 -1 ⇒ .1. : .1. OK
  • ⇒ .. : .. OK
  • foo ⇒ .. : .foo. FAILED
  • 1fo ⇒ .. : .1fo. FAILED
  • 1 foo ⇒ .1. : .1. OK
  • 1 2.5 3 ⇒ .3. : .3. OK

Test parametrů 🗁 args/11 🗋 data
🗋 script
./test

script
  • #!/usr/bin/env bash
  • printf '%s\n' "$@" | sort -ns | tail -n 1
  • ./test
  • 2 1 ⇒ .2. : .2. OK
  • 2 2 ⇒ .2. : .2. OK
  • 11 9 ⇒ .11. : .11. OK
  • 9 09 ⇒ .09. : .9. FAILED
  • 2 ⇒ .2. : .2. OK
  • -0 ⇒ .-0. : .-0. OK
  • 2 3 1 ⇒ .3. : .3. OK
  • 1 -1 ⇒ .1. : .1. OK
  • ⇒ .. : .. OK
  • foo ⇒ .. : .foo. FAILED
  • 1fo ⇒ .. : .1fo. FAILED
  • 1 foo ⇒ .1. : .1. OK
  • 1 2.5 3 ⇒ .3. : .3. OK
  • ./test
  • 2 1 ⇒ .2. : .2. OK
  • 2 2 ⇒ .2. : .2. OK
  • 11 9 ⇒ .11. : .11. OK
  • 9 09 ⇒ .09. : .09. OK
  • 2 ⇒ .2. : .2. OK
  • -0 ⇒ .-0. : .-0. OK
  • 2 3 1 ⇒ .3. : .3. OK
  • 1 -1 ⇒ .1. : .1. OK
  • ⇒ .. : .. OK
  • foo ⇒ .. : .foo. FAILED
  • 1fo ⇒ .. : .1fo. FAILED
  • 1 foo ⇒ .1. : .1. OK
  • 1 2.5 3 ⇒ .3. : .3. OK

Test parametrů 🗁 args/12 🗋 data
🗋 script
./test

test
  • ⫶
  • out=$( ./script "${args[@]}" 2>/dev/null/tmp/err )
  • EC=$?
  • err=$( < /tmp/err )
  • r_EC=FAILED
  • [[ $EC == "$exp_EC" ]] && r_EC=OK
  • r_out=FAILED
  • [[ $out == "$exp_out" ]] && r_out=OK
  • r_err=FAILED
  • [[ $err == "$exp_err" ]] && r_err=OK
  • printf '%-10s ⇒ %-5s : %-5s %s| %-6s : %-6s %-6s | %d %d %-6s\n' \
  • "${args[*]}" ".$exp_out." ".$out." "$r_out" \
  • ".$exp_err." ".$err." "$r_err" \
  • "$exp_EC" "$EC" "$r_EC"
  • ⫶
  • ./test
  • 2 1 ⇒ .2. : .2. OK | .. : .. OK | 0 0 OK
  • 2 2 ⇒ .2. : .2. OK | .. : .. OK | 0 0 OK
  • 11 9 ⇒ .11. : .11. OK | .. : .. OK | 0 0 OK
  • 9 09 ⇒ .09. : .09. OK | .. : .. OK | 0 0 OK
  • 2 ⇒ .2. : .2. OK | .. : .. OK | 0 0 OK
  • -0 ⇒ .-0. : .-0. OK | .. : .. OK | 0 0 OK
  • 2 3 1 ⇒ .3. : .3. OK | .. : .. OK | 0 0 OK
  • 1 -1 ⇒ .1. : .1. OK | .. : .. OK | 0 0 OK
  • ⇒ .. : .. OK | .Args. : .. FAILED | 2 0 FAILED
  • foo ⇒ .. : .foo. FAILED | .NaN. : .. FAILED | 1 0 FAILED
  • 1fo ⇒ .. : .1fo. FAILED | .NaN. : .. FAILED | 1 0 FAILED
  • 1 foo ⇒ .1. : .1. OK | .NaN. : .. FAILED | 1 0 FAILED
  • 1 2.5 3 ⇒ .3. : .3. OK | .NaN. : .. FAILED | 1 0 FAILED

Test parametrů 🗁 args/13 🗋 data
🗋 script
./test

  • #!/usr/bin/env bash
  • args=()
  • err=0
  • if (( $# == 0 )); then
  • echo "Args" >&2
  • exit 2
  • fi
  • for arg; do
  • if [[ $arg =~ ^-?[0-9]+$ ]]; then
  • args+=( "$arg" )
  • elif (( err == 0 )); then
  • echo "NaN" >&2
  • err=1
  • fi
  • done
  • printf '%s\n' "${args[@]}" | sort -ns | tail -n 1
  • exit $err
  • ./test
  • 2 1 ⇒ .2. : .2. OK | .. : .. OK | 0 0 OK
  • 2 2 ⇒ .2. : .2. OK | .. : .. OK | 0 0 OK
  • 11 9 ⇒ .11. : .11. OK | .. : .. OK | 0 0 OK
  • 9 09 ⇒ .09. : .09. OK | .. : .. OK | 0 0 OK
  • 2 ⇒ .2. : .2. OK | .. : .. OK | 0 0 OK
  • -0 ⇒ .-0. : .-0. OK | .. : .. OK | 0 0 OK
  • 2 3 1 ⇒ .3. : .3. OK | .. : .. OK | 0 0 OK
  • 1 -1 ⇒ .1. : .1. OK | .. : .. OK | 0 0 OK
  • ⇒ .. : .. OK | .Args. : .Args. OK | 2 2 OK
  • foo ⇒ .. : .. OK | .NaN. : .NaN. OK | 1 1 OK
  • 1fo ⇒ .. : .. OK | .NaN. : .NaN. OK | 1 1 OK
  • 1 foo ⇒ .1. : .1. OK | .NaN. : .NaN. OK | 1 1 OK
  • 1 2.5 3 ⇒ .3. : .3. OK | .NaN. : .NaN. OK | 1 1 OK

Test souborů 🗁 file/00 🗋 Dockerfile
🗋 docker-compose.yml

  • Skript backup vytvoří adresář …
  • Pro vývoj a testování vytvoříme kontejner dev
    • Popis image: Dockerfile
    • konfigurace kontejneru: docker-compose.yml

Test souborů 🗁 file/01 ./build
./run

  • Sestavení image
  • podman-compose build \
  • --build-arg U=$(id -u) --build-arg G=$(id -g)
  • Spuštění kontejneru
  • podman-compose run --rm dev
FROM debian:bookworm-slim
ARG U=1000
ARG G=1000
RUN groupadd -g $G tester \
&& useradd -u $U -g tester -s /bin/bash -m tester
WORKDIR /workspace
RUN chown -R tester:tester /workspace
USER tester
CMD ["/bin/bash"]
services:
dev:
build: .
image: dev
userns_mode: "keep-id"
volumes:
- .:/workspace:O

Test souborů 🗁 file/02 🗋 test-01
🗋 test-02
🗋 test-03
./run

  • Test v hostiteli
  • #!/usr/bin/env bash
  • echo "❔ Workspace writable"
  • podman-compose run --rm dev ./test-01
  • if [[ ! -d testdir ]]; then
  • echo " ✅ Overlay works"
  • else
  • echo " ❎ Overlay does not work"
  • fi
  • echo "❔ Root directory read-only"
  • podman-compose run --rm dev ./test-02
  • echo "❔ Unprivileged user"
  • podman-compose run --rm dev ./test-03
  • ./run
#!/usr/bin/env bash
mkdir testdir 2>/dev/null
if [[ -d testdir ]]; then
echo " ✅ Directory created"
else
echo " ❎ Directory not created"
fi
#!/usr/bin/env bash
mkdir /testdir 2>/dev/null
if [[ ! -d /testdir ]]; then
echo " ✅ Directory not created"
else
echo " ❎ Directory created"
fi
#!/usr/bin/env bash
if (( $(id -u) != 0 )); then
echo " ✅ UID != 0"
else
echo " ❎ UID == 0"
fi