Очень часто при автоматизации тестовых сценариев можно натолкнуться на шаги вида: "повторите шаги 1-12" или еще интересней, "повторите все шаги" + некоторые поправки. То есть в конечном итоге при реализации нам надо будет полностью продублировать код определенных шагов. А излишнее дублирование может вызвать ряд трудностей при модификациях тестов, при поддержке тестов. Любые изменения надо вносить во все вхождения повторяющегося кода. А это дополнительная потеря времени. Как этого избежать:


  1. Вынесение повторяющегося кода в отдельные модули(функции) - разумное решение, соответствующее смыслу этих самых модулей(функций). Опять же, это может стать основой для формирования библиотек, которые можно переносить из одного проекта в другой (зависит от абстрагированности тех или иных компонент от конкретного приложения). Как это выглядит на простейшем примере. Рассмотрим некоторый фрагмент кода (пример на JScript для TestComplete, но по аналогии можно распространить на любые другие аналогичные языки и средства):

    function SomeTest() {

    // Step 1
    // Print "Step 1"
    Log.Message( "Step 1" );

    // Step 2
    // Print "Step 2"
    Log.Message( "Step 2" );

    // Step 3
    // Repeat steps 1-2
    Log.Message( "Step 1" );
    Log.Message( "Step 2" );
    }


    Это простейший пример, но на нем можно показать принцип. Вместо обычного вывода на печать могут быть весьма сложные конструкции, но принцип от этого мало зависит. Как можно этот фрагмент сделать более устойчивым к изменениям функционала. Мы можем код шагов 1 и 2 сделать отдельными функциями:

    function Step1() {

    Log.Message( "Step 1" );
    }

    function Step2() {

    Log.Message( "Step 2" );
    }

    function SomeTest() {

    // Step 1
    // Print "Step 1"
    Step1();

    // Step 2
    // Print "Step 2"
    Step2();

    // Step 3
    // Repeat steps 1-2
    Step1();
    Step2();
    }


    Конкретно в подобном случае, когда накрывается достаточно большая серия шагов, можно поступить в виде:

    function Steps_1_2() {

    Log.Message( "Step 1" );
    Log.Message( "Step 2" );
    }

    function SomeTest() {

    // Step 1
    // Print "Step 1"
    Steps_1_2();

    // Step 2
    // Print "Step 2"
    // *** see above

    // Step 3
    // Repeat steps 1-2
    Steps_1_2();
    }


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

    Преимущества подобных решений:

    • Минимизация дублирования больших участков кода (изменение затрагивает не более 2-х участков)
    • Сокращение записи - один вызов функции зачастую меньше занимает строчек, чем многократное дублирование кода

    Недостатки:

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

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

    function SomeTestDriver( iStart, iEnd, aParam ) {

    var i;

    for( i = iStart ; i <= iEnd ; i++ ) {

    switch( i ) {

    case 1:
    // Step 1
    // Print "Step 1"
    Log.Message( "Step 1" );
    break;
    case 2:
    // Step 2
    // Print "Step 2"
    Log.Message( "Step 2" );
    break;
    case 3:
    // Step 3
    // Repeat steps 1-2
    SomeTestDriver( 1 , 2 , aParam );
    break;

    }
    }
    }

    function SomeTest() {

    SomeTestDriver( 1 , 3 , null );
    }


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

    Преимущества подхода:

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


    Недостатки:

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



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

Для того, чтобы оставлять комментарии, вы должны войти под своим логином.