Unit-тестирование в SWI Prolog

      Комментарии к записи Unit-тестирование в SWI Prolog отключены

В этой теме 0 ответов, 1 участник, последнее обновление  Васильев Владимир Сергеевич 1 неделя назад.

  • Автор
    Сообщения
  • #3932

    Не так давно на блоге мы говорили о модульном тестировании (Unit-тестировании) и о подходе к разработке через тестирование, но в тот раз использовался С++ и Boost Unit Test framework: Юнит-тестирование. Пример. Boost Unit Test. В той статье можно посмотреть общую теорию по теме тестирования. Вчера на форуме был задан вопрос о модульном тестировании в SWI Prolog, разберемся в этом на примере двух функций из этой темы: «Подсчитать отрицательные числа, сформировать список позиций на Prolog«.

    Для начала, обратим внимание на то, что функцию формирования списка неудобно использовать (можно неправильно вызвать) — в качестве второго аргумента она ожидает получить пустой список. Напишем вспомогательную функцию:

    negative_positions(List, Positions):-
      negative_positions(List, 0, Positions).

    Модульное тестирование, несмотря на название, слабо связана с понятием модулей в SWI Prolog. Тем не менее, код проекта (который сдается заказчику) обычно пишется (и хранится) отдельно от кода тестов. С одной стороны такое разделение делает более чистым ваш код, а с другой некоторые тесты могут работать не так быстро, как хотелось бы — такие тесты также целесообразно хранить в отдельных модулях. Модульная структура позволяет скрыть от пользователя вспомогательные функции, с помощью которых реализуется интерфейс модуля, доступный пользователю. Так, в нашем случае можно скрыть от пользователя функцию negative_positions/3. Для этого в начало файла negative.pl добавим строку:
    :- module(negative, [negative_count/2, negative_positions/2]).

    Теперь приступим к написанию тестов. Тестировать будем две функции, для каждой из них можно проверить несколько «особых» случаев, например, подать на вход пустой список, список без отрицательных элементов и список с нулевым элементом. Кроме того, можно сделать более сложный пример с некоторой «случайной» последовательностью. В фал negative_test.pl поместим следующий код:

    :- begin_tests(negative_count).
    :- use_module(negative).
    test(empty):-
      negative_count([], 0).
    test(no_negative):-
      negative_count([1,2,3,4], 0).
    test(zero):-
      negative_count([0,0,0], 0).
    test(random):-
      negative_count([-1,24,5,23,-6,4], 2).
    :- end_tests(negative_count).
    
    :- begin_tests(negative_positions).
    :- use_module(negative).
    test(empty):-
      negative_positions([], []).
    test(no_negative):-
      negative_positions([1,2,3,4], []).
    test(zero):-
      negative_positions([0,0,0], []).
    test(random):-
      negative_positions([-1,24,5,23,-6,4], [1,5]).
    :- end_tests(negative_positions).

    Тестовый модуль может содержать несколько тестовых наборов, каждый из которых помещен между командами begin_tests и end_tests. У нас таких набора два — по одному на каждую функцию. Тестовый набор имеет имя, которое поможет найти ошибку в случае провала теста.

    Тестовый набор может содержать описания нескольких тестовых случаев, каждый из них содержит одно обращение к тестируемому коду и проверку корректности результата. Тестовый набор также имеет имя.

    Чтобы запустить тесты, достаточно загрузить в интерпретатор файл с тестами и вызвать команду run_tests. Сделаем это:

    Видно, что два теста провалены. На самом деле, этот код содержал ошибку (если вы сейчас возьмете код из статьи, то ошибки уже не будет, но чтобы посмотреть как это работает ошибку можно внести намеренно):

        negative_positions([], _, []):-!.
        negative_positions([Head|Tail], CurPos, Positions):-
          NextPos is CurPos + 1, (
            Head > 0, !, 
            negative_positions(Tail, NextPos, Positions);
            
            negative_positions(Tail, NextPos, TailPositions), 
            Positions = [NextPos|TailPositions]
          ).

    Элементы равные нулю тут рассматриваются как отрицательные. Исправить это можно заменой знака сравнения с > на >=. Не забудьте после изменения кода заново загрузить в интерпретатор файл с кодом, иначе там будет старая версия (без изменений):

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

Для ответа в этой теме необходимо авторизоваться.