Покритие на кода
Накратко: Покритието на операторите пита дали всеки ред код е изпълнен. Покритието на разклоненията пита дали всяко решение е минало и по двата си пътя. За функцията за доставката първото иска 2 теста, второто 3. 100 % разклонения включва 100 % оператори, но не и обратното. И никое покритие не казва, че резултатът е проверен или че границата е на мястото си.
Проблем
100 % покритие, грешна такса
Програмистът пуска тестовете на доставката с инструмент, който брои кои редове са изпълнени. Тестовете са два: 0 € и 60 €. Отчетът казва: 100 % от редовете.
На следващия ден клиентите с количка за 30 € плащат 4,99 € доставка, вместо 4,90 €. Редът с таксата беше изпълнен. Никой тест не видя какво пише в него.
Идеята
Бялата кутия
Досега тестовете идваха от правилото: това е черната кутия. Техниките на бялата кутия (white-box) взимат тестовете от кода: кои редове има и по кои пътища може да мине програмата.
Не трябва да можеш да пишеш код за този урок. Трябва да можеш да прочетеш функцията за доставката. Езикът е по избор, в табовете:
static String delivery(double total) { if (total <= 0) { return "отказана"; } double fee = 4.90; if (total >= 50) { fee = 0; } return String.format("%.2f €", fee);}static string Delivery(double total){ if (total <= 0) { return "отказана"; } double fee = 4.90; if (total >= 50) { fee = 0; } return $"{fee:F2} €";}def delivery(total): if total <= 0: return "отказана" fee = 4.90 if total >= 50: fee = 0 return f"{fee:.2f} €"function delivery(total) { if (total <= 0) { return "отказана"; } let fee = 4.9; if (total >= 50) { fee = 0; } return `${fee.toFixed(2)} €`;}Функцията има две решения (места, където програмата избира път): „сумата е 0 или по-малко“ и „сумата е от 50 нагоре“. Второто е „ако“ без „иначе“: ако е невярно, програмата просто прескача реда, който нулира таксата.
Идеята
Покритие на операторите
Покритието на операторите (statement coverage) пита: колко от редовете, които правят нещо, е изпълнил поне един тест?
Ето двата теста от началото, като извиквания на функцията, с изхода им:
System.out.println("0 € → " + delivery(0));System.out.println("60 € → " + delivery(60));0 € → отказана60 € → 0.00 €Console.WriteLine("0 € → " + Delivery(0));Console.WriteLine("60 € → " + Delivery(60));0 € → отказана60 € → 0.00 €print("0 € →", delivery(0))print("60 € →", delivery(60))0 € → отказана60 € → 0.00 €console.log("0 € → " + delivery(0));console.log("60 € → " + delivery(60));0 € → отказана60 € → 0.00 €- 0 € минава през първата проверка и връща „отказана“.
- 60 € минава през всичко останало: таксата става 4,90, после проверката „от 50“ я прави 0, после връщането.
Всеки ред е изпълнен. Покритието на операторите е 100 %, с два теста.
Идеята
Покритие на разклоненията
Покритието на разклоненията (branch coverage) пита друго: всяко решение минало ли е и по двата си пътя?
Разклоненията тук са четири: първата проверка вярна и невярна, втората вярна и невярна. 0 € минава първата като вярна. 60 € минава първата като невярна и втората като вярна. Втората като невярна не я минава никой: това е празният път на „ако“ без „иначе“. Покритието е 3 от 4, тоест 75 %.
Трети тест, с платена доставка, затваря дупката:
System.out.println("0 € → " + delivery(0));System.out.println("30 € → " + delivery(30));System.out.println("60 € → " + delivery(60));0 € → отказана30 € → 4.90 €60 € → 0.00 €Console.WriteLine("0 € → " + Delivery(0));Console.WriteLine("30 € → " + Delivery(30));Console.WriteLine("60 € → " + Delivery(60));0 € → отказана30 € → 4.90 €60 € → 0.00 €print("0 € →", delivery(0))print("30 € →", delivery(30))print("60 € →", delivery(60))0 € → отказана30 € → 4.90 €60 € → 0.00 €console.log("0 € → " + delivery(0));console.log("30 € → " + delivery(30));console.log("60 € → " + delivery(60));0 € → отказана30 € → 4.90 €60 € → 0.00 €Три теста, 100 % от разклоненията. И отговорът на въпроса на урока: за 100 % покритие на разклоненията на функцията за доставката трябват 3 теста.
По-точно, за любопитните
ISTQB CTFL v4.0.1 описва двете мерила в раздел 4.3: statement testing и branch testing. Покритието е брой изпълнени оператори (или минати разклонения) върху всички, в проценти. Програмата казва и връзката им: 100 % покритие на разклоненията включва 100 % покритие на операторите, но не и обратното.
Има и по-строги мерила: покритие на условията (всяко условие вътре в решението е видяно и вярно, и невярно) и MC/DC (всяко условие е показало, че само то може да смени решението). MC/DC се иска за най-критичния софтуер в авиацията. Те не са в CTFL v4.0.
Покритието се мери с инструмент: JaCoCo за Java, coverlet за C#, coverage.py за Python, c8 или вграденото в Node за JavaScript. Тестовете се пускат, а инструментът отбелязва кои редове и разклонения са минати.
Пробвай
Колко теста?
Идеята
Защо 100 % оператори пропусна таксата
Ето функцията от началото, с таксата 4,99:
static String delivery(double total) { if (total <= 0) { return "отказана"; } double fee = 4.99; if (total >= 50) { fee = 0; } return String.format("%.2f €", fee);}static string Delivery(double total){ if (total <= 0) { return "отказана"; } double fee = 4.99; if (total >= 50) { fee = 0; } return $"{fee:F2} €";}def delivery(total): if total <= 0: return "отказана" fee = 4.99 if total >= 50: fee = 0 return f"{fee:.2f} €"function delivery(total) { if (total <= 0) { return "отказана"; } let fee = 4.99; if (total >= 50) { fee = 0; } return `${fee.toFixed(2)} €`;}С двата теста за операторите изходът е същият като на правилната функция:
System.out.println("0 € → " + delivery(0));System.out.println("60 € → " + delivery(60));0 € → отказана60 € → 0.00 €Console.WriteLine("0 € → " + Delivery(0));Console.WriteLine("60 € → " + Delivery(60));0 € → отказана60 € → 0.00 €print("0 € →", delivery(0))print("60 € →", delivery(60))0 € → отказана60 € → 0.00 €console.log("0 € → " + delivery(0));console.log("60 € → " + delivery(60));0 € → отказана60 € → 0.00 €При 60 € редът с таксата се изпълнява, но веднага след него таксата става 0. Грешната стойност никога не стига до изхода. Изпълнен не значи проверен.
Третият тест, този за пълните разклонения, минава по празния път и показва таксата:
System.out.println("0 € → " + delivery(0));System.out.println("30 € → " + delivery(30));System.out.println("60 € → " + delivery(60));0 € → отказана30 € → 4.99 €60 € → 0.00 €Console.WriteLine("0 € → " + Delivery(0));Console.WriteLine("30 € → " + Delivery(30));Console.WriteLine("60 € → " + Delivery(60));0 € → отказана30 € → 4.99 €60 € → 0.00 €print("0 € →", delivery(0))print("30 € →", delivery(30))print("60 € →", delivery(60))0 € → отказана30 € → 4.99 €60 € → 0.00 €console.log("0 € → " + delivery(0));console.log("30 € → " + delivery(30));console.log("60 € → " + delivery(60));0 € → отказана30 € → 4.99 €60 € → 0.00 €Затова покритието на разклоненията е по-силно. Празният път на „ако“ е точно мястото, където стойност от горния ред стига до клиента.
Идеята
Какво покритието не казва
Ето проверката на лимита в количката: добавянето се отказва, ако е по-малко от 1 бройка или ако в количката ще станат повече от 10.
static boolean canAdd(int inCart, int add) { if (add < 1 || inCart + add > 10) { return false; } return true;}static bool CanAdd(int inCart, int add){ if (add < 1 || inCart + add > 10) { return false; } return true;}def can_add(in_cart, add): if add < 1 or in_cart + add > 10: return False return Truefunction canAdd(inCart, add) { if (add < 1 || inCart + add > 10) { return false; } return true;}Две извиквания дават 100 % от разклоненията: (5, 0) минава през „отказ“, (2, 3) през „прието“.
System.out.println(canAdd(5, 0));System.out.println(canAdd(2, 3));falsetrueConsole.WriteLine(CanAdd(5, 0));Console.WriteLine(CanAdd(2, 3));FalseTrueprint(can_add(5, 0))print(can_add(2, 3))FalseTrueconsole.log(canAdd(5, 0));console.log(canAdd(2, 3));falsetrueСега смени „> 10“ на „> 11“. Същите два теста минават, покритието е пак 100 %, а 11-ата бройка влиза в количката. Покритието мери какво е изпълнено, не колко близо до границата. Тестът (10, 1) от урока за граничните стойности хваща грешката.
И нещо, което никое покритие не може да види: липсващ код. Ако проверката за празна количка изобщо не е написана, няма ред, който да не е покрит. 100 % покритие на код, който не прави нужното, е пак 100 %. Затова бялата кутия не заменя черната. Покритието казва къде няма тестове, а не че тестовете са добри.
Упражнение
Покритието на лимита
1. Две условия, едно решение. Въпросите са за проверката на лимита по-горе.
Тест
Провери се
Свързани уроци
Раздел „Свързани уроци“- Нужно отпреди: Защо рано · Класове на еквивалентност · Гранични стойности · Таблица на решения
- Продължава в: Тестване по опит
- Задачи по темата: Техники за тестов дизайн