Input-Output testování

Každý program obsahuje jeden chybný řádek. Každý program jde zkrátit o jeden řádek. Z toho plyne, že každý program jde zkrátit na jeden řádek, který je chybný.

— Murphyho zákon o programování

Jak jste si už jistě všimli, i v relativně jednoduchých programech lze udělat nemalé množství chyb. Naštěstí testovat programy, které pouze berou vstup a vrací výstup, není až tak složité. Stačí napsat generátor vstupů, for cyklus a pár podmínek, kterými budeme vyhodnocovat výstup, a máme základní testování…

Tak jednoduché to bohužel není – ale nápad je to dobrý. Abychom vás podpořili v psaní si vlastních testů, ať už ručně psaných, nebo generovaných, dáváme vám k dispozici dva python skripty, které vám to mohou usnadnit.

Soubor process_handler.py obsahuje interní logiku, která za vás ošetřuje některé okrajové případy. Ze třídy ProcessHandler je pro vás důležitá pouze statická metoda run.

Váš vlastní kód doporučujeme psát do separátního souboru, například do test_your_c_program.py. V něm se můžete podívat na ukázku, jak metodu run volat a jak lze případně psát assert (pravidla kontrolující určité podmínky). Jakmile si oba soubory stáhnete z odkazu, který je níže, klidně si rovnou zkuste spustit ukázku z terminálu pomocí python3 test_your_c_program.py. V ukázce testujeme chování programu base64. Pokud jste na Windows, klidně si zaměňte volání za jiný program dle svého výběru.

A jak to tedy využít? Doporučujeme si úkoly testovat po každém z následujících kroků:

  1. Zkopírovat si do testovátka všechny testy uvedené v zadání.

  2. Napsat si alespoň pár vlastních testů, a to na validní vstupy, okrajové případy a případně i nevalidní vstupy.

  3. Napsat si generátor vstupů a zkusit, zda váš Céčkový program na některém z nich nepadá. V tuto chvíli stačí, že nepadá, výstup moc kontrolovat nemusíte.

  4. Pokud všechny předchozí části prošly, doporučujeme přejít na Aisu. Tam zkoušejte pouštět program a vzorovou implementaci na stejných vstupech, a tyto výstupy porovnávat.

  5. Profit?

A pak samozřejmě znovu spustit testy vždy, když uděláte nějakou (větší) úpravu svého Céčkového kódu.

Mějte na paměti, že sdílené fakultní stroje používá hodně lidí, a buďte k nim ohleduplní. Je to nejen slušné chování, ale taky se tím vyhnete například dočasné blokaci účtu. Pro více informací se podívejte na příslušnou stránku na fakultním webu. Příkaz nice už za vás řeší ProcessHandler.

Jak použít testovátko

První krok je si testovátko stáhnout.

process_handler.py

Interní kód testovátka

test_your_c_program.py

Ukázka, jak testovátko volat.

Je psané tak, aby stačilo interagovat se statickou metodou ProcessHandler.run. Ta má jeden povinný a tři volitelné parametry a vrací tříprvkový Tuple obsahující:

  • návratový kód programu,

  • string s stdout,

  • string pro stderr.

def run(command: List[str],
        input_str: Optional[str] = None,
        print_output: bool = True,
        timeout: int = 5  # seconds
      ) -> Tuple[int, str, str]:

Prvním (a jediným povinným) parametrem je command, který očekává seznam řetězců. První prvek je adresa souboru, který chceme spustit. Případné další prvky seznamu jsou jednotlivé argumenty.

Pojďme si to ukázat rovnou na příkladu. Následujícím způsobem můžete programu předat argumenty. První příkaz fungovat bude, druhý však ne. Spouštíme totiž program přímo, bez pomoci shellu (např. bash). Tedy nějaká funkčnost, na kterou jste zvyklí, není k dispozici.

from process_handler import ProcessHandler

ProcessHandler.run(["ls", ".", "-a", "-l", "--human-readable", "--width=50"])  # this works
ProcessHandler.run(["ls", "~", "-a", "-l", "--human-readable", "--width=50"])  # this doesn't, because we don't have ~ outside of shell

Je možné spouštět i příkazy skrz shell, to ale nedoporučujeme. Nad příkazy spuštěnými přímo má testovátko větší kontrolu.

from process_handler import ProcessHandler

ProcessHandler.run(['/bin/sh', '-c', 'echo Lorem Ipsum'], print_output=True, timeout=0.5)

Zbývá už tedy jen zmínit, jak lze předávat vstup a jak pracovat s výstupem.

from typing import List
from process_handler import ProcessHandler

command_to_execute: List[str] = ["base64"]
input_str: str = f"Lorem ipsum from stdin"
return_code, outs, errs = ProcessHandler.run(command_to_execute, input_str=input_str, print_output=True)

assert return_code == 0
assert "TG9yZW0gaXBzdW0gZnJvbSBzdGRpbg==" in outs  # beware different newlines based on system
assert len(errs) == 0

Jak testovat svůj vlastní kód, a nejen již existující programy jako ls nebo base64? Úplně stejně, stačí jen změnit cestu k souboru, který se má spustit. Pokud kompilujete ručně pomocí gcc a nezvolíte vlastní jméno pro výstupní soubor, tak se automaticky pojmenuje a.out. Pokud kompilujete skrz CMake, který dodáváme ke cvičením, nejspíše bude výstupní program v adresáři cmake-build-debug a bude se jmenovat dle aktuálního úkolu, např. calc. Pokud jste na Windows, tak to nejspíše bude něco jako calc.exe. Věříme, že to už zvládnete dohledat.

from process_handler import ProcessHandler

ProcessHandler.run(['./a.out'])
ProcessHandler.run(['./cmake-build-debug/calc'])
ProcessHandler.run(['./cmake-build-debug/calc.exe'])

Zbývá poslední otázka – jak si generovat testy. To necháme na vás. Věříme, že si s Pythonem poradíte.

Na závěr připomeneme, že testovátko lze použít několika různými způsoby. Doporučujeme si vyzkoušet všechny:

  • Testovat vůči ručně psaným testům.

  • Testovat pomocí generovaných testů (fuzzing) a to jak na správnost, tak na stabilitu programu.

  • Testovat vůči vzorové implementaci

Tak vzhůru do toho, půl testů je hotovo!