Разработка

Часть 3: Разработка через тестирование, TDD (Тестирование ПО)

Оглавление


Блочное тестирование уже укоренилось в качестве полезной практики работы с кодом. Протестированный код дает разработчикам уверенность в том, что результат отвечает намерению. Методика разработки, управляемой тестами — это следующий шаг, заключающийся в том, что тесты пишутся раньше, чем код.

Мизерные затраты на написание программ, тестирующих другие программы позволяют нам применять этот способ верификации на всех этапах тестирования: блочное, функциональное, комплексное и приемочное.

Читать далее

Реклама
linux

Fedora перестает грузиться на UEFI после обновения (и показывает MOK)

Никогда бы не подумал, но вчера столкнулся с проблемой при которой после обновления fedora начисто отказалась загружаться постоянно выдавая при старте окно MokManager с просьбой добавить ключи или хеши с secureboot.

Что меня больше всего удивило так это то, что efibootmgr -v выдавал кучу записей загрузчиков shim.efi с некорректными uuid разделов на которых они размещены.

$ efibootmgr -v
BootCurrent: 0002
Timeout: 0 seconds
BootOrder: 0007,0002,2001,2002,2003
Boot0000* Fedora    HD(1,GPT,f627bf87-5440-4997-8310-aa80dba7e383,0x800,0x64000)/File(\EFI\Fedora\shim.efi)
Boot0001* Fedora    HD(1,GPT,f627bf87-5440-4997-8310-aa80dba7e383,0x800,0x64000)/File(\EFI\fedora\shim.efi)
Boot0002* Linux    PciRoot(0x0)/Pci(0x1c,0x4)/Pci(0x0,0x0)/NVMe(0x1,00-00-00-00-00-00-00-00)/HD(1,GPT,f627bf87-5440-4997-8310-aa80dba7e383,0x800,0x64000)/File(\EFI\BOOT\BOOTX64.EFI)A01 ..
Boot0003* Fedora    HD(1,GPT,f627bf87-5440-4997-8310-aa80dba7e383,0x800,0x64000)/File(\EFI\fedora\shim.efi)
Boot0004* Fedora    HD(1,GPT,f627bf87-5440-4997-8310-aa80dba7e383,0x800,0x64000)/File(\EFI\fedora\shim.efi)
Boot0005* Fedora    HD(1,GPT,f627bf87-5440-4997-8310-aa80dba7e383,0x800,0x64000)/File(\EFI\fedora\shim.efi)
Boot0006* Fedora    HD(1,GPT,f627bf87-5440-4997-8310-aa80dba7e383,0x800,0x64000)/File(\EFI\fedora\shim.efi)
Boot0007* Fedora    HD(1,GPT,f627bf87-5440-4997-8310-aa80dba7e383,0x800,0x64000)/File(\EFI\fedora\shim.efi)
Boot2001* EFI USB Device    RC
Boot2002* EFI DVD/CDROM    RC
Boot2003* EFI Network    RC

Конечно в данном листинге уже все верно поскольку он был сделан на рабочей машине, но в оригинальном листинге в идентификаторе HD были прописаны несуществующие uuid разделов. И подобных записей было далеко за 20 штук.

К сожалению мне неизвестна причина по которой система прописывает неверные данные, но мне нужно было оживить машину. Для этого следует сначала зайти в chroot окружение убитой системы.

Далее нам потребуется удалить все записи загрузчика с неверными данными. Это записи вида Boot0ХХХ.

Сначала надо переустановить grub-efi и shim как это рекомендует документация.

# dnf reinstall grub-efi shim

Теперь удаляем невалидные записи. Для их удаления нам потребуется выполнять команду

# efibootmgr -B -b XXXX
  • -B — удалить запись
  • -b XXXX — выбрать активной запись XXXX

В качестве XXXX будут выступать идентификаторы неугодных записей (не трогайте записи, которые начинаются не с нуля — они системные). И конечно же перед каждым удалением следите за состоянием записей (efibootmgr -v).

Последним шагом будет добавление правильной записи.

efibootmgr -c -w -L Fedora -d /dev/nvme0n1 -p 1 -l '\EFI\Fedora\shim.efi'
  • -c — создать запись
  • -w — сделать запись в mbr если это требуется
  • -L Fedora — метка новой записи в загрузчике
  • -d /dev/nvme0n1 — жесткий диск на котором размещен efi-раздел (у вас может быть /dev/sda или любой другой)
  • -p 1 — номер раздела на диске (если efi у вас это /dev/sda1, то 1, sda2 — 2 и т.д.)
  • -l ‘\EFI\Fedora\shim.efi’ — расположение файла загрузчика относительно корня диска efi (а не корня файловой системы в которую он подмонтирован). Обратите внимание, что тут нам обязательно надо указать загрузчик shim.efi, а не что-то другое.

После завершения можно перезагружаться и пробовать войти в систему. Mok Manager больше не должен появляться. Если это не так, то где-то вы допустили ошибку.

Литература

Разработка

Часть 2: Тестирование простого приложения (Тестирование ПО)

Оглавление

Первое приложение

Программа считывает три целочисленных значения из консоли. Эти значения интерпретируются как длины сторон треугольника. Программа выводит сообщение о том, каким является данный треугольник — разносторонним, равнобедренным или равносторонним.

Напишем, функцию, которая принимает на вход три стороны треугольника, которые заданы целыми числами и возвращает тип треугольника. Сохраним написанный код в файле triangle.php.

/**
 * Не треугольник
 */
define('TRIANGLE_BAD', 0);
/**
 * Равносторонний треугольник
 */
define('TRIANGLE_EQUILATERAL', 1);
/**
 * Прямоугольный треугольник
 */
define('TRIANGLE_RIGHT', 2);
/**
 * Равнобедренный треугольник
 */
define('TRIANGLE_ISOSCELES', 3);
/**
 * Разносторонний треугольник
 */
define('TRIANGLE_SIDED', 4);

/**
 * По длинам сторон $a, $b и $c возвращает тип треугольника.
 * Если стороны не являются целочисленными, то выбрасывает исключение.
 *
 * @param $a
 * @param $b
 * @param $c
 * @return int
 * @throws Exception
 */
function triangle_type($a, $b, $c)
{
   // Вполне ожидаемо,
   // что нецелочисленные значения должны приводить к исключительной ситуации.
   if (!is_int($a) or !is_int($b) or !is_int($c))
   {
       throw new \Exception('Invalid triangle definition');
   }

  $max = null;
  $min1 = null;
  $min2 = null;

  if (($a+$b)>$c and ($a+$c)>$b and ($b+$c)>$a)
  {
     if (($a>$b) and ($a>$c))
     {
        $max = $a;
        $min1 = $b;
        $min2 = $c;
     }
     else if (($b>$c) and ($b>$a))
     {
        $max = $b;
        $min1 = $a;
        $min2 = $c;
     }
     else
     {
        $max = $c;
        $min1 = $a;
        $min2 = $b;
     }

     if (pow($max, 2) == pow($min1, 2) + pow($min2, 2))
     {
        return TRIANGLE_RIGHT;
     }
       else if (($max==$min1) and ($max==$min2))
       {
           return TRIANGLE_EQUILATERAL;
       }
     else if (($max==$min1) or ($max==$min2) or ($min1==$min2))
     {
        return TRIANGLE_ISOSCELES;
     }
     else
     {
        return TRIANGLE_SIDED;
     }
  }
  else
  {
     return TRIANGLE_BAD;
  }
}

Функция достаточно тривиальна, поэтому мы не будем останавливаться на ее реализации. Нас будет интересовать, как найти в ней ошибки.

Для начала потребуется реализовать механизм, который позволит вводить данные с консоли и получать результат. Сохраним следующий код в файле main.php. Чуть позже вы поймете, почему мы используем разные файлы для самой функции и для кода, который обрабатывает пользовательский ввод.

// здесь мы подключим ранее написанную функцию для определения типа треугольника
require __DIR__ . DIRECTORY_SEPARATOR . 'triangle.php';

function main()
{
   // проинициализируем переменные
   $a = $b = $c = 0;

   // получим длины сторон со стандартного ввода
   $num = fscanf(STDIN, "%d %d %d\n", $a, $b, $c);
   // если мы смогли считать длины трех сторон,
   // то вызовем нашу функцию и покажем результат
   if ($num == 3)
   {
       switch (triangle_type($a, $b, $c))
       {
           case TRIANGLE_BAD:
               echo "Это не треугольник\n";
               break;
           case TRIANGLE_EQUILATERAL:
               echo "Это равносторонний треугольник\n";
               break;
           case TRIANGLE_ISOSCELES:
               echo "Это равнобедренный треугольник\n";
               break;
           case TRIANGLE_RIGHT:
               echo "Это прямоугольный треугольник\n";
               break;
           case TRIANGLE_SIDED:
               echo "Это разносторонний треугольник\n";
               break;
       }
   }
}

main();

Код также достаточно тривиален. Теперь мы можем запустить полученное приложение (да, это именно приложение — последовательность инструкций, определяющих процедуру решения конкретной задачи компьютером).

Откроем терминал, перейдем в каталог, с проектом и выполним следующую команду (для того, чтобы все сработало у вас должен быть установлен интерпретатор php в системе).

$ php main.php

Программа будет ожидать ввод трех чисел, разделенных пробелами.

И вот что мы можем увидеть на экране.

null

Поэкспериментируйте немного с программой вводя разные наборы чисел.

А теперь рассмотрим эту программу с точки зрения разработчика, которому досталось ее тестировать. Какие наборы тестов он должен разработать, чтобы отыскать все возможные баги? Прежде чем читать дальше подумайте и попробуйте посчитать то количество, которое придумали вы.

Итак. Ниже приведен набор тестовых сценариев, которые должны быть написаны для нашей функции.

  1. тест для проверки действительно неравностороннего треугольника (наборы [1, 2, 3], [2, 5, 10] треугольниками не являются).
  2. проверка на действительно равносторонний треугольник
  3. проверка на равнобедренный треугольник (наборы вида [2, 2, 4] треугольником не являются)
  4. как минимум три теста для проверки равнобедренного треугольника, которые представляют собой перестановки одного и того же набора чисел ([3, 3, 4], [3, 4, 3], [4, 3, 3])
  5. тест на нулевую длину одной из сторон
  6. тест на сторону, имеющую длину меньше нуля
  7. проверка набора чисел, в котором сумма длин двух сторон равна третьей
  8. тест перестановок для троек чисел из теста 7
  9. проверка набора чисел, в котором сумма длин двух сторон меньше третьей ([12, 15, 30])
  10. тест перестановок для троек чисел из теста 9
  11. проверка на нулевую длину всех трех сторон
  12. проверка на передачу нецелочисленных значений
  13. проверка на передачу неполного набора значений
  14. проверка не только входных данных, но и ожидаемого выходного значения в каждом из тестов 1-13

Если вы не смогли назвать все кейсы, то не пугайтесь. Среднее число тестов, которые называли в разное время опытные разработчики составило 7,8.

Конечно нет никаких гарантий того, что набор тестов, удовлетворяющих перечисленным условиям, обнаружит все возможные ошибки. Но поскольку случаи 1-13 представляют ошибки, реально встречающиеся в различных версиях данной программы, адекватное тестирование должно обнаружить хотя бы их.

Это упражнение должно было продемонстрировать вам, что тестирование простых программ наподобие вышеприведенной является отнюдь не тривиальной задачей. А теперь попытайтесь представить себе, насколько трудоемким окажется тестирование, скажем, бухгалтерской программы крупного предприятия, компилятора или же системы управления воздушным движением, объем кода которых может достигать сотен тысяч строк. Еще большие трудности возникают с приложениями, которые написаны с использованием объектно-ориентированных языков (куда входит и php) и подходов. В частности, тесты для подобных приложений должны выявлять ошибки с созданием экземпляров объектов и взаимодействия между ними.

Однако, какой бы устрашающей ни казалась задача, адекватное (достаточно полное) тестирование программ является ключевой и, как вы убедитесь далее, вполне реализуемой частью процесса разработки программного обеспечения.

Тестируем

Конечно же самым простым решением будет просто закодировать все тестовые случаи для нашего проекта и написать нечто вроде следующего кода (файл triangle_test_simple.php).

require __DIR__ . DIRECTORY_SEPARATOR . 'triangle.php';

function testForIsoscelesTriangle()
{
   echo "Test for [3, 4, 4]: ";
   if (triangle_type(3, 4, 4) == TRIANGLE_ISOSCELES) {
       echo "ok\n";
   } else {
       echo "fail\n";
   }
}

function main()
{
   testForIsoscelesTriangle();
}

main();

И такое часто практикуется. Особенно в среде разработчиков на C\C++. На каждый логически связанный набор тестовых случаев создается свой файл. Который содержит множество функций обрабатывающих по одному сценарию каждая.

В этом нет ничего плохого. Единственный минус такого подхода — отсутствие готового инструментария, который реализует все необходимые операции по обслуживанию и запуску тестов. Весь инструментарий приходится для каждого проекта реализовывать заново. Либо изготавливать свою собственную обвязку, которая будет кочевать из проекта в проект.

Один из вариантов создания инструмента для работы с подобными тестами вы можете увидеть в файле triangle_test.php. Запустите его и увидите на экране подробный лог тестирования проекта.

null

Литература

Исходные тексты программ

Оглавление

Разработка

Часть 1: краткая историческая справка (Тестирование ПО)

Оглавление

Баг. Кто он?

Мы все слышали слово «баг», которым обозначается ошибка в программе приводящая к неверному результату.

Баг. Кто он?

Известно, что первыми, кто назвал неправильную работу устройств «насекомым» были инженеры телеграфных компаний. И было это задолго до появления первых компьютеров — еще в 19 веке.

Откуда пошло тестирование

На заре развития вычислительной техники считалось хорошим тоном проверять программу на всех вариантах входных данных. Поскольку большинство программ разрабатывалось для нужд военно-промышленных комплексов, то тестирование было выделено в отдельную строго формализованную процедуру, которая включала в себя описание всех тестовых наборов данных, тестовых функцию и всевозможных реакций программы.

В 1970-х годах процесс проверки программы обозначался как «доказательство правильности». И включал в себя проверку всех возможных путей исполнения кода.

По мере усложнения программ и решаемых задач тестирование становилось все сложнее и сложнее. И вот уже «доказательство правильности» становится недостаточно всеобъемлющей процедурой. Ибо проверить абсолютно все физически невозможно. Да, тогда и сегодня все еще используется подобных подход, но очень редко и чаще всего в аэрокосмической промышленности.

В дальнейшем было отмечено, что исчерпывающее тестирование практически невозможно.

В эпоху процессора 8086 тестирование стало подразумевать еще понятием «предупреждение дефектов». А с 1990-х годов в это понятие стали включать планирование, проектирование, создание, поддержку и выполнение тестов и тестовых окружений, и это означало переход от тестирования к обеспечению качества, охватывающего весь цикл разработки программного обеспечения.

Формальное определение

Очень часто от инженеров можно услышать определения, которые коверкают саму суть процесса:

  • Тестирование — это процесс, демонстрирующий отсутствие ошибок в программе.
  • Цель тестирования — показать, что программа корректно выполняет заданные функции.
  • Тестирование — это процесс, позволяющий убедиться в том, что программа реализует запланированные действия.

Но эти определения перевернуты с ног на голову — программу тестируют не для того, чтобы продемонстрировать отсутствие ошибок. Программу тестируют для того, чтобы найти ошибки.

Поэтому правильнее будет назвать следующее определение:

Тестирование это процесс выполнения программы с целью обнаружения ошибок.

Виды тестирования (по знанию устройства ПО)

Тестирование методом «черного ящика»

Или другими словами «тестирование, управляемое данными» (data-driven testing).

В соответствии с этим методом программа рассматривается как черный ящик, внутреннее поведение и устройство которого не имеет никакого значения.

При этом подходе разработчики фокусируются на выяснении обстоятельств, при которых поведение программы не соответствует спецификации.

Тестирование методом «белого ящика»

Стратегия тестирования, которая управляется логикой. Суть данного подхода заключается в анализе кода или логики работы процедуры и подборе такого набора входных данных, который позволит покрыть абсолютно все пути исполнения программы.

Это определение нельзя назвать полным, поскольку подобранный набор данных не всегда соответствует спецификации, но это наиболее полное определение, которое дает представление о тестировании методом «белого ящика».

Виды тестирования по уровню изолированности компонент

Конечно же здесь перечислены не все виды тестирования и это следует понимать. Остановимся лишь на тех этапах тестирования, которое чаще всего применяется в веб-разработке.

Блочное тестирование (юнит-тестирование)

Этот аспект проверяет самые мелкие аспекты работы системы. Например корректность каждого метода класса или отдельной функции.

Также отдельные аспекты проверки логики работы модулей входят в эту фазу тестирования. Например проверка функционала регистрации\авторизации пользователя на уровне объекта «пользователь».

Этот этап является проверкой методом «белого ящика».

Интеграционное тестирование

После того, как модули программы прошли предварительную проверку они собираются в функциональные группы и проверяются аспекты бизнес-логики. Данные этап — это тестирование методом «черного ящика». Тесты проектируются таким образом, чтобы проверить соответствие полученного результата заявленному.

Функциональное тестирование

Этот этап тестирование является тестированием методом черного ящика. Этап проверяем отсутствие ошибок в реализации спецификации ПО. На входы форм подаются значения, и выполняется проверка данных на выходе тем, что описаны в спецификации.

Приемочное тестирование

Ваше приложение развертывается в окружении, конфигурация которого близка к реальной. А также на данных, которые являются или соответствуют реальным.

Некоторые недальновидные разработчики возлагают этот вид тестирования на пользователей (альфа- и бета-тестеры). Порой это оправдано и каждый из этих случаев нужно рассматривать по отдельности, но чаще всего так делать не стоит.

Суть заключается в том, чтобы проверить, насколько реализованный функционал соответствует заявленным ожиданиям.

Этот вид тестирования так же является тестированием методом «черного ящика».

Литература

  1. Книга «Искусство тестирования программ» Гленфорд Майерс, Том Баджетт, Кори Сандлер, ISBN 978-5-8459-1974-8
  2. Wikipedia: Software testing

Оглавление