Описание языка JavaTESK

версия 1.0


Оглавление

// development storage for h1 level headers


1. Введение

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

Документ предназначен пользователям инструмента JavaTESK и входит в комплект документации, поставляемой с ним.

Документ состоит из следующих разделов:

В документе используются следующие шрифтовые и цветовые выделения:

Дополнительную информацию по инструменту JavaTESK и технологии тестирования UniTESK можно найти в следующих источниках:

2. Общие сведения о технологии тестирования UniTESK

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

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

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

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

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

Как описать достаточный набор тестовых ситуаций?

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

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

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

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

Описание тестовой последовательности называется тестовым сценарием.

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

3. Общие сведения о языке JavaTESK

Язык JavaTESK является расширением языка программирования Java и специально разработан для поддержки технологии тестирования UniTESK.

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

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

3.1. Специальные классы

В язык JavaTESK введены четыре специальных вида классов:

Их объявления должны находится в файлах с расширением sej.

Для всех видов специальных классов должны выполняться следующие общие семантические ограничения.

Семантические ограничения

  1. Специальный класс всегда рассматривается как public.
  2. Правила расположения специальных классов в sej-файлах такие же, как и правила расположения типов Java (классов, интерфейсов, аннотаций и перечислений).
    1. в файле должно быть не более одного publiс класса, все равно, обычного, спецификационного, медиаторного, класса-медиатора реакций или сценарного;
    2. имя public класса, если он есть, должно совпадать с именем файла без расширения.
  3. Пространство имен для всех классов одно.
  4. Ограничения на комбинации модификаторов те же, что и для Java классов.
  5. Специальный класс не может быть вложенным классом.
  6. Ограничения на типовые параметры, наследование и реализацию интерфейсов аналогичны ограничениям для Java классов. Однако для некоторых видов специальных классов могут формулироваться дополнительные ограничения.
  7. Обычный Java-класс не может быть наследником специального класса.

3.2. Специальные методы

Виды специальных методов:

Семантические ограничения

  1. Специальные методы всегда рассматриваются как public члены объёмлющих их классов.
  2. Ограничения на комбинации модификаторов те же, что и для Java методов - нет повторений, кроме аннотаций.
  3. Ограничения, связанные с использованием параметров и типовых параметров в теле специального метода, те же, что и в Java - например, нельзя присваивать в final параметр.
  4. Указание размерности возвращаемого результата-массива после параметров запрещено (основание - замечание спецификации Java о том, что это оставлено только для совместимости и не рекомендуется использовать в новом коде)
  5. При наследовании специальные методы не могут быть перегружены (overriden by) обычными Java-методами.

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

3.3. Специальные операторы

В язык JavaTESK введены два дополнительных оператора:

Оператор импликации введен для удобства чтения и записи логических выражений. Он является бинарным инфиксным оператором, приоритет которого меньше приоритета оператора дизъюнкции ||, но больше приоритета условного оператора ?:.

Выражение x => y  эквивалентно выражению !x || y, и при его вычислении, также как при вычислении других логических операторов, действуют правила короткой логики.

Оператор импликации ассоциативен справа налево, то есть выражение x => y => z эквивалентно выражению x => (y => z).

3.4. Специальные ключевые слова

В язык JavaTESK введены дополнительные ключевые слова, которые нельзя использовать в качестве идентификаторов:

4. Спецификации

Спецификации представляют собой формальное описание функциональных требований к целевой системе в форме инвариантов данных и спецификационных методов (см. разделы Инварианты данных и Спецификационные методы соответственно).

Следуя концепии объектно-ориентированного программирования, реализованной в языке программирования Java, спецификации на языке JavaTESK пишутся в виде набора спецификационных классов (см. раздел Спецификационные классы).

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

Привязка данных спецификационной модели к данным целевой системы и спецификационных методов к операциям целевой системы осуществляется при помощи медиаторов (см. раздел Медиаторы).

4.1. Спецификационные классы

Назначение

Спецификационные классы являются основными структурными единицами спецификации.

Обычно каждый спецификационный класс содержит описание требований к некоторому компоненту целевой системы.

Описание

Спецификационные классы оформляются как обычные классы Java и могут содержать следующие объявления:

Язык JavaTESK поддерживает наследование и уточнение спецификационных классов (см. разделы Наследование спецификационных классов и Уточнение спецификационных классов соответственно).

Синтаксис

SE_SpecificationClassDeclaration
          ::= SE_ClassModifiers
              "specification"
              UnmodifiedClassHeader
              (
                SE_ClassRefinement
                ( "," SE_ClassRefinement )*
              )?
              SE_SpecificationClassBody
          ;

SE_ClassModifiers
          ::= (   "abstract"
                | "strictfp"
                | "final"
                | Annotation
              )*
          ;

UnmodifiedClassHeader
          ::= "class"
              Identifier
              ( TypeParameters )?
              ( Super )?
              ( Interfaces )?
          ;

SE_SpecificationClassBody
          ::= "{"
              ( SE_SpecificationClassBodyDeclaration )*
              "}"
          ;

SE_SpecificationClassBodyDeclaration
          ::=   ClassBodyDeclaration
              | SE_InvariantDeclaration
              | SE_OperationSpecification
          ;

SE_OperationSpecification
          ::=   SE_MethodSpecification
              | SE_ReactionSpecification
          ;

Грамматику элементов SE_InvariantDeclaration и SE_MethodSpecification можно найти в разделах Инварианты данных и Спецификационные методы соответственно.

Семантические ограничения

  1. Должны выполняться общие семантические ограничения для всех видов специальных классов.
  2. Спецификационные классы не должны быть final.
  3. Дополнительные ограничения описаны в разделе Наследование спецификационных классов.

Пример

// объявление спецификационного класса AccountSpecification
specification class AccountSpecification
{
  // объявление полей спецификационной модели данных
  static public int maximumCredit;

  public int balance;

  // объявление конструктора спецификационного
  // класса AccountSpecification
  public AccountSpecification()
  {
  }

  // объявление инварианта данных I
  invariant I()
  {
    return balance >= -maximumCredit;
  }

  // объявление спецификационного метода deposit
  specification void deposit(int sum)
  {
    // предусловие спецификационного метода deposit
    pre
    {
      return (0 < sum ) && (balance <= Integer.MAX_VALUE - sum);
    }

    // постусловие спецификационного метода deposit
    post
    {
      branch Single;

      return balance == pre balance + sum;
    }
  }

  // объявление спецификационного метода withdraw
  specification int withdraw(int sum)
  {
    // предусловие спецификационного метода withdraw
    pre { return sum > 0; }

    // постусловие спецификационного метода withdraw
    post
    {
      if(balance < sum - maximumCredit)
      {
        branch TooLargeSum("Withdrawn sum is too large");

        return balance == pre balance && withdraw == 0;
      }
      else
      {
        branch Normal("Successful withdrawal");

        return balance == pre balance - sum && withdraw == sum;
      }
    }
  }
}

4.2. Инварианты данных

Назначение

Инварианты данных предназначены для описания ограничений на значения полей спецификационной модели данных (см. раздел Спецификации).

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

Описание

Инварианты данных могут объявляться только в спецификационных классах и представляются как методы без параметров, помеченные модификатором invariant и возвращающие значение типа boolean: true – если данные удовлетворяют ограничениям, false – иначе. Поскольку тип возвращаемого значения  фиксирован, он не указывается.

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

Инварианты данных могут быть статическими. Такие инварианты помечаются модификатором static и могут описывать ограничения только на статические поля спецификационной модели данных.

Синтаксис

SE_InvariantDeclaration
          ::= ( SE_CommonModifier | "prime" )*
              "invariant"
              Identifier
              "("
              ( <STRING_LITERAL> )?
              ")"
              Block
          ;

SE_CommonModifier
          ::=   "static"
              | "strictfp"
              | Annotation
          ;

Семантические ограничения

Определение: в блоке Block существует необработанный ThrowStatement, если

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

  1. Должны выполняться общие семантические требования к специальным методам.
  2. Инвариант не может реализовывать методы, наследуемые от интерфейсов.
  3. Если инвариант перегружает (overrides) некоторый метод из базового класса, то этот базовый метод тоже должен быть инвариантом.
  4. Внутри static инвариантов, как и внутри static методов, не должны использоваться нестатические элементы класса и this .
  5. Block должен возвращать значение типа boolean
  6. Block не должен содержать необработанных ThrowStatement.
  7. Инвариант нельзя вызывать.

Пример

// объявление спецификационного класса TimeSpecification
specification class TimeSpecification
{
  // объявление полей спецификационной модели данных
  int hour;
  int minute;

  // объявление инварианта, описывающего ограничения 
  // на значения поля hour
  invariant hours()
  {
    // часы могут изменяться в пределах от 0 до 23
    return hour >= 0 && hour <= 23;
  }

  // объявление инварианта, описывающего ограничения 
  // на значения поля minute
  invariant minutes()
  {
    // минуты могут изменяться в пределах от 0 до 59
    return minute >= 0 && minute <= 59;
  }
  ...
}

4.3. Спецификационные методы

Назначение

Спецификационные методы предназначены для описания поведения операций целевой cистемы в форме пред- и постусловий (см. разделы Предусловие и Постусловие соответственно).

Описание

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

Спецификационные методы могут перегружать спецификационные методы, а также наследовать и уточнять их функциональность (см. раздел Связи между спецификациями).

Спецификационные методы могут быть перегружены медиаторными методами медиаторных классов (см. раздел Медиаторные классы).

Объявление спецификационного метода включает:

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

Синтаксис

SE_MethodSpecification
          ::= (   SE_CommonModifier
                | "synchronized"
              )*
              "specification"
              SE_MethodHeader
              SE_MethodBody
          ;

SE_MethodHeader
          ::= MethodHeader
              ( SE_OperationRefinement
                ( "," SE_OperationRefinement )*
              )?
          ;

MethodHeader ::= ( TypeParameters )?
                 ResultType
                 MethodDeclarator
                 ( Throws )?
             ;

Грамматику элементов SE_PreCondition и SE_PostCondition можно найти в разделах Предусловие и Постусловие соответственно.

Семантические ограничения

  1. Должны выполняться общие семантические требования к специальным методам.
  2. Если сигнатура данного метода (имя + список типов параметров, с заменой ... на [], + количество и ограничения на типовые параметры) совпадает с сигнатурой одного из методов в предках данного класса или в реализуемых им интерфейсах (далее называем ближайший такой метод методом-предком), то должны быть выполнены следующие ограничения:
    1. Метод-предок должен быть спецификационным методом или должен быть декларирован в интерфейсе, реализуемом данным классом.
    2. Остальные ограничения на соотношение описателей данного спецификационного метода и его метода-предка аналогичны ограничениям из языка Java :
      1. Метод-предок не должен быть final
      2. Тип результата метода-предка и тип результата данного метода должны находиться в отношении, определяемом используемой версией языка Java.
      3. Типы исключений данного метода (с учетом указанных unchecked исключений) должны быть подмножеством типов исключений метода-предка
      4. Спецификационный метод и его метод-предок должны быть одновременно static или не static
  3. Имена параметров спецификационного метода не должны совпадать с именем самого метода.

Пример

// объявление спецификационного метода enq, описывающего
// операцию добавления элемента в очередь
specification void enq(Object obj)
{
  // предусловие спецификационного метода enq
  pre
  {
    return obj != null;
  }

  // постусловие спецификационного метода enq
  post
  {
    // определяем единственную ветвь функциональности
    branch Single;

    List new_items = (List)(pre items.clone());
    new_items.addLastElement(obj);

    return items.equals(new_items);
  }
}

4.4. Описание возможных исключений

Назначение

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

Описание

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

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

Синтаксис

Throws ::= "throws" ExceptionTypeList ;

ExceptionTypeList ::= ExceptionType ( "," ExceptionType )* ;

ExceptionType
          ::=   ClassType
              | TypeVariable
          ;

Семантические ограничения

  1. Ограничения те же, что и для исключений обычных методов Java.
  2. Дополнительное требование: в качестве типов исключений не могут фигурировать типовые переменные

Пример

  // объявление спецификационного метода pop
  specification Object pop()
    // описание возможных исключений
    throws StackIsEmptyException
  {
    ...
  }

4.5. Тело спецификационного метода

Назначение

Описание

Определение: первый список BlockStatement называется блоком инициализаций.

Определение: второй список BlockStatement называется реализационным телом.

Синтаксис

SE_MethodBody
          ::= "{"
              ( BlockStatement )*
              ( SE_PreCondition )?
              ( SE_PostCondition )?
              ( BlockStatement )*
              "}"
          ;

Семантические ограничения

  1. Блок инициализации должен удовлетворять следующим ограничениям:
    1. В нем не должно быть ReturnStatement.
    2. В нем не должно быть необработанных ThrowStatement.
    3. Его конец (место после последней инструкции) должен быть достижим.
    4. В нём запрещено декларировать переменные с именем, совпадающим с именем спецификационного метода.
  2. Реализационное тело должно быть правильно построенным по правилам Java телом метода с сигнатурой данной операции.
  3. Если SE_PreCondition и SE_PostCondition отсутствуют, все инструкции относятся к реализационному телу.

4.6. Предусловие

Назначение

Предусловие служит для выделения ситуаций, в которых определено поведение операции целевой системы, описываемой в спецификационном методе (см. раздел Спецификационные методы).

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

Описание

Предусловие оформляется как набор инструкций, представляющий собой тело метода, имеющего те же параметры, что и спецификационный метод и возвращающего результат типа boolean: true – если при данных значениях параметров поведение операции целевой системы определено, false – иначе.

Этот набор инструкций заключается в фигурные скобки и помечается ключевым словом pre.

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

Синтаксис

SE_PreCondition
          ::= "pre" Block ;

Семантические ограничения

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

Предусловие должно удовлетворять следующим ограничениям:

  1. Block должен возвращать значение типа boolean.
  2. Внутри Block не должно быть необработанных ThrowStatement.
  3. Операторы break, относящиеся к существенным SwitchStatement или LabeledStatement, тоже должны быть в существенном контексте. Конструкция LabeledStatement, непосредственно помечающая конструкции, перечисленные в определении понятия существенного контекста, не считается существенной.
  4. ReturnStatement должна встречаться только в существенном контексте.
  5. Внутри элементарных формул, не должны использоваться операции присваивания, пре- и пост- инкремента и декремента. (Таким образом на тривиальном уровне ограничивается возможность появления побочных эффектов (side-effects) при вычислении элементарных формул.)

Пример

// объявление статического спецификационного метода log
static specification double log(double x)
{
  // предусловие спецификационного метода log
  pre { return x > 0; }
  ...
}

4.7. Постусловие

Назначение

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

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

Описание

Постусловие оформляется как набор инструкций, представляющий собой тело метода, имеющего те же параметры, что и спецификационный метод и возвращающего результат типа boolean: true – если поведение операции целевой системы соответствует спецификации, false – иначе.

Этот набор инструкций заключается в фигурные скобки и помечается ключевым словом post.

В постусловии должны быть определены ветви функциональности при помощи операторов branch (см. раздел Оператор branch).

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

Синтаксис

SE_PostCondition
          ::= "post" Block ;

Семантические ограничения

  1. Для постусловия должны выполняться все ограничения, накладываемые на предусловие.
  2. В рамках Block либо не должно быть ни одного SE_BranchStatement, либо на каждом пути по графу потока управления от входа в постусловие до выхода из него должно встречаться ровно одно SE_BranchStatement.
    Если операторов branch вообще нет в постусловии, считается, что перед первой его инструкцией стоит branch SINGLE ;
  3. Запрещено декларировать переменные с именем, совпадающим с именем спецификационного метода.
  4. Если тип результата отличен от void, то вводится неявная локальная переменная, тип которой совпадает с типом возвращаемого методом значения, а имя совпадает с именем метода. На использование этой переменной накладываются следующие ограничения:
    1. Переменная может использоваться только после SE_BranchStatement по потоку управления.
    2. Переменная рассматривается как final;
    3. Переменная не должна использоваться в превыражениях.
  5. Выражение thrown ...
    1. может использоваться только в пост-условиях модельных операций, содержащих в заголовках своих деклараций Throws часть;
    2. может использоваться только после SE_BranchStatement по потоку управления;
    3. не является lvalue;
    4. имеет тип java.lang.Throwable ;
    5. не должно использоваться в превыражениях.

Пример

// объявление статического спецификационного метода log
static specification double log(double x)
  // описание возможных исключений
  throws IllegalArgumentException
{
  // постусловие спецификационного метода log
  post
  {
    if(x <= 0)
    {
      // определение ветви функциональности
      // для случая, когда аргумент не входит в
      // область определения спецификационного метода
      branch Exceptional;

      return thrown != null &&
        thrown instanceof IllegalArgumentException;
    }
    else
    {
      // определение ветви функциональности
      // для случая, когда аргумент входит в 
      // область определения спецификационного метода
      branch Normal;

      return thrown == null &&
        (Math.power(Math.E, log) - x) / x < 1.e-12;
    }
  }
}

4.8. Оператор branch

Назначение

Оператор branch предназначен для задания ветвей функциональности в постусловиях спецификационных методов (см. раздел Постусловие).

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

Оператор branch используются для разбиения всех тестовых ситуаций в покрытии ветвей функциональности (см. раздел Покрытие ветвей функциональности).

Описание

Оператор branch имеет два параметра – идентификатор, служащий для именования ветви функциональности, и строковый литерал - для указания дополнительного комментария.

Идентификаторы, используемые в операторах branch, должны быть уникальными в пределах постусловия спецификационного метода.

Каждое вхождение оператора branch трактуется как место обращения к операции целевой системы. Все инструкции, написанные до оператора branch по потоку управления, выполняются до обращения к операции, после оператора branch – после ее выполнения.

Исключения составляют превыражения (см. раздел Превыражения), помечаемые префиксным оператором pre: они должны располагаться после оператора branch по потоку управления, но вычисляются до выполнения операции целевой системы.

Если оператор branch содержит оператор update, то обновлённое состояния модели, построенное этим оператором update, будет проверяться инвариантами.

Синтаксис

SE_BranchStatement
          ::= "branch"
Identifier
( "(" Expression ")" )? ( ";" | SE_UpdateStatement
;

Семантические ограничения

  1. SE_BranchStatement допустима только в рамках SE_PostCondition, вложенного в SE_MethodBody спецификационной операции.
  2. SE_BranchStatement должна находиться в существенном контексте.
  3. Идентификаторы, используемые в различных SE_BranchStatement внутри SE_PostCondition, должны быть разными.
  4. Если есть Expression, то оно должно иметь тип, неявно приводимый к java.lang.String
  5. Считается, что оператор update, вложенный в оператор branch, следует за последним по потоку управления.

Пример

// объявление спецификационного метода withdraw
specification int withdraw(int sum)
{
  // предусловие спецификационного метода withdraw
  pre { return sum > 0; }

  // постусловие спецификационного метода withdraw
  post
  {
    if(balance < sum - maximumCredit)
    {
      // определение ветви функциональности
      // для случая, когда снимаемая сумма слишком большая, 
      // то есть при ее снятии происходит превышение 
      // максимального допустимого размера кредита
      branch TooLargeSum("Withdrawn sum is too large");

      return balance == pre balance && withdraw == 0;
    }
    else
    {
      // определение ветви функциональности
      // для случая, когда при снятии указанной суммы 
      // не происходит превышение максимального
      // допустимого размера кредита
      branch Normal("Successful withdrawal");

      return balance == pre balance - sum && withdraw == sum;
    }
  }
}

4.9. Оператор mark

Назначение

Оператор mark предназначен для пометки путей в пред- и постусловиях спецификационных методов. Он используются для разбиения всех тестовых ситуаций в покрытии помеченных путей (см. раздел Покрытие помеченных путей).

Описание

Оператор mark имеет единственный параметр – строковый литерал, именующий пометку пути.

Синтаксис

SE_MarkStatement
          ::= "mark"
              ( Identifier )?
              <STRING_LITERAL>
          ;

Семантические ограничения

  1. Оператор mark можно использовать только в пред- и постусловиях спецификационных методов
  2. Оператор mark должен находиться в существенном контексте (см. раздел Модификатор irrelevant)
// объявление спецификационного метода withdraw
specification int withdraw(int sum)
{
  // предусловие спецификационного метода withdraw
  pre { return sum > 0; }

  // постусловие спецификационного метода withdraw
  post
  {
    // помечаем пути в зависимости от значения поля balance
    if(balance > 0)
      mark "Withdrawal from account with positive balance";
    else if(balance == 0)
      mark "Withdrawal from empty account";
    else
      mark "Withdrawal from account with negative balance";

    if(balance < sum - maximumCredit)
    {
      // определение ветви функциональности
      // для случая, когда снимаемая сумма слишком большая,
      // то есть при ее снятии происходит превышение
      // максимального допустимого размера кредита
      branch TooLargeSum("Withdrawn sum is too large");

      return balance == pre balance && withdraw == 0;
    }
    else
    {
      // определение ветви функциональности
      // для случая, когда при снятии указанной суммы
      // не происходит превышение максимального
      // допустимого размера кредита
      branch Normal("Successful withdrawal");

      return balance == pre balance - sum && withdraw == sum;
    }
  }
}

4.10. Превыражения

Назначение

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

Значение выражения до выполнения операции целевой системы называется его пре-значением, после – пост-значением.

Описание

Превыражения помечаются при помощи префиксного оператора pre и должны располагаться после оператора branch по потоку управления.

При записи превыражений следует руководствоваться следующими правилами:

Синтаксис

SE_PreExpression ::= "pre" UnaryExpression ;

UnaryExpression
          ::=   PreIncrementExpression
              | PreDecrementExpression
              | UnaryPlusExpression
              | UnaryMinusExpression
              | UnaryExpressionNotPlusMinus
          ;

PreIncrementExpression ::= "++" UnaryExpression ;

PreDecrementExpression ::= "--" UnaryExpression ;

UnaryPlusExpression ::= "+" UnaryExpression ;

UnaryMinusExpression ::= "-" UnaryExpression ;

UnaryExpressionNotPlusMinus
         ::=   PostfixExpression
             | BitwiseComplementExpression
             | LogicalComplementExpression
             | CastExpression
             | SE_PreExpression
         ;

Семантические ограничения

Определение: тип языка Java является синтаксически выразимым, если он не является wildcard-ом или массивом wildcard-ов.

  1. Может встречаться только в рамках SE_PostCondition,
    1. Если в нем есть операторы branch, то только после хотя бы одного из них по потоку управления.
    2. Если в нем нет операторов branch, то везде.
  2. Любая переменная, используемая в превыражении, должна быть объявлена и определена в точке расположения любого из операторов branch, предшествующих данному превыражению по потоку управления.
  3. Тип вложенного UnaryExpression должен быть синтаксически выразим.
  4. Имеет тип, совпадающий с типом UnaryExpression.
  5. Если одно превыражение вложено в другое, то транслятор языка JavaTESK дожен выдать предупреждение.

Пример

// объявление спецификационного метода deposit
specification void deposit(int sum)
{
  // предусловие спецификационного метода deposit
  pre
  {
    return (0 < sum ) && (balance <= Integer.MAX_VALUE - sum);
  }

  // постусловие спецификационного метода deposit
  post
  {
    // баланс счета после вызова метода (balance) равен
    // балансу счета до вызова метода (pre balance),
    // увеличенному на сумму вклада (sum)
    return balance == ( pre balance ) + sum;
  }
}

5. Описание тестового покрытия

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

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

На языке JavaTESK тестовое покрытие описывается в терминах спецификации. В языке определены следующие стандартные тестовые покрытия:

5.1. Покрытие ветвей функциональности

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

Ветви функциональности помечаются при помощи оператора branch (см. раздел Оператор branch).

Пример

// объявление спецификационного метода enq,
// описывающего добавление элемента в очередь
specification void enq(Object obj)
{
  post
  {
    // если очередь заполнена или obj равен null
    if(items.size() == MAX_SIZE || obj == null)
    {
      branch NoObjectAdded;

      // элемент не добавляется
      return items.equals(pre items.clone());
    }
    else
    {
      branch ObjectAdded;

      // элемент добавляется в конец очереди
      List new_items = (List)(pre items.clone());
      new_items.addLastElement(obj);
      return items.equals(new_items);
    }
  }
}

Для данного примера покрытие ветвей функциональности выделяет два вида ситуаций:

5.2 Покрытие помеченных путей

В покрытии помеченных путей к одному виду относятся ситуации, соответствующие одной и той же последовательности операторов mark и branch (см. разделы Оператор mark и Оператор branch соответственно), оканчивающейся оператором branch, в пред- и постусловии одного спецификационного метода (см. раздел Спецификационные методы).

Пример

// объявление спецификационного метода enq,
// описывающего добавление элемента в очередь
specification void enq(Object obj)
{
  post
  {
    // если очередь заполнена или obj равен null
    if(items.size() == MAX_SIZE || obj == null)
    {
      branch NoObjectsAdded;

      // элемент не добавляется 
      return items.equals(pre items.clone());
    }
    else
    {
      // выделяем ситуацию, когда очередь пуста
      if(items.size() == 0) mark "Empty queue";

      branch ObjectAdded;

      // элемент добавляется в конец очереди
      List new_items = (List)(pre items.clone());
      new_items.addLastElement(obj);
      return items.equals(new_items);
    }
  }
}

Для данного примера покрытие помеченных путей выделяет три вида ситуаций:

5.2. Покрытие предикатов

В покрытии предикатов к одному виду относятся ситуации, соответствующие одному и тому же пути по потоку управления в пред- и постусловии одного спецификационного метода (см. раздел Спецификационные методы), оканчивающемуся оператором branch (см. раздел Оператор branch).

Пример

// объявление спецификационного метода enq,
// описывающего добавление элемента в очередь
specification void enq(Object obj)
{
  post
  {
    // если очередь заполнена или obj равен null
    if(items.size() == MAX_SIZE || obj == null)
    {
      branch NoObjectsAdded;

      // элемент не добавляется 
      return items.equals(pre items.clone());
    }
    else
    {
      // выделяем ситуацию, когда очередь пуста
      if(items.size() == 0) mark "Empty queue";

      branch ObjectAdded;

      // элемент добавляется в конец очереди
      List new_items = (List)(pre items.clone());
      new_items.addLastElement(obj);
      return items.equals(new_items);
    }
  }
}

Для данного примера покрытие предикатов выделяет три вида ситуаций:

5.3 Покрытие дизъюнктов

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

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

Элементарной логической формулой называется логическое выражение, участвующее

Такое выражение должно быть элементарным, то есть не должно быть построено из других логических выражений при помощи операций конъюнкции && или &, дизъюнкции || или |, отрицания !, импликации =>, равенства ==, неравенства != или условного оператора ?:.

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

В силу смысловых связей между элементарными логическими формулами не все комбинации их значений являются достижимыми, что приводит к невозможности достижения 100% покрытия дизъюнктов. Для указания таких связей служит оператор tautology (см. раздел Оператор tautology).

Пример

// объявление спецификационного метода enq,
// описывающего добавление элемента в очередь
specification void enq(Object obj)
{
  post
  {
    // если очередь заполнена или obj равен null
    if(items.size() == MAX_SIZE || obj == null)
    {
      branch NoObjectsAdded;

      // элемент не добавляется 
      return items.equals(pre items.clone());
    }
    else
    {
      // выделяем ситуацию, когда очередь пуста
      if(items.size() == 0) mark "Empty queue";

      branch ObjectAdded;

      // элемент добавляется в конец очереди
      List new_items = (List)(pre items.clone());
      new_items.addLastElement(obj);
      return items.equals(new_items);
    }
  }
}

Для данного примера покрытие дизъюнктов выделяет четыре вида ситуаций, соответствующие следующим наборам значений элементарных логических формул (items.size() == MAX_SIZE), (obj == null) и (items.size() == 0):

5.4. Модификатор irrelevant

Назначение

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

Описание

Модификатор irrelevant может использоваться только внутри пред- и постусловий спецификационных методов (см. разделы Спецификационные методы ).

Помимо блоков, помеченных модификатором irrelevant, несущественными блоками являются блоки циклов и блоки try, catch и finally.

Внутри несущественных блоков нельзя использовать операторы branch и mark, так как они используются для определения тестового покрытия (см. разделы Оператор branch и Оператор mark соответственно), а также операторы return.

Синтаксис

SE_IrrelevantStatement
          ::= "irrelevant" Statement ;

Семантические ограничения

  1. Модификатор irrelevant может использоваться только внутри пред- и постусловий спецификационных методов.

Пример

// объявление статического спецификационного метода max
specification static int max(int x, int y)
{
  // объявление постусловия спецификационного метода max
  post
  {
    int z = x;

    // несущественный блок, содержащий вспомогательные вычисления
    irrelevant
    {
      if(x < y)
        z = y;
    }

    return max == z;
  }
}

5.5. Оператор tautology

Назначение

Смысловые связи между элементарными логическими формулами (см. раздел Покрытие дизъюнктов) приводят к тому, что не все комбинации их значений являются достижимыми, что искажает тестовое покрытие (см. раздел Описание тестового покрытия). Например, комбинация month(date) == 2 и day(date) == 31, где month – метод, возвращающий номер месяца, а day – день, недостижима.

Оператор tautology предназначен для указания таких смысловых связей между элементарными логическими формулами.

Описание

Оператор tautology имеет единственный параметр, являющийся логическим выражением.

Он утверждает, что указанное выражение всегда принимает значение true в силу смысловых связей между участвующими в нем элементарными логическими формулами.

Синтаксис

SE_TautologyStatement
          ::= "tautology" Expression ";" ;

Семантические ограничения

  1. Оператор tautology допустим только в рамках блока инициализации, предусловия и постусловия спецификационного метода.
  2. Выражение в операторе tautology должно иметь тип boolean. Для Java5 также доспустим тип java.lang.Boolean.
  3. Оператор tautology должен находиться в существенном контексте.
  4. Запрещено использование оператора tautology в постусловиях, не содержащих операторов branch.
  5. Если оператор tautology находится в постусловии, содержащем хотя бы один оператор branch, то оператор tautology должен следовать до любого из операторов branch по потоку управления.
  6. В выражении оператора tautology не должны использоваться операции присваивания, а также пре- и пост- инкремента и декремента. (Таким образом на тривиальном уровне ограничивается возможность появления побочных эффектов (side-effects) при вычислении выражения тавтологии.)

Пример

// объявление спецификационного класса EmployeeManagementSpecification
specification class EmployeeManagementSpecification
{
  // объявление спецификационного метода getSalary,
  // описывающего операцию, возвращающего зарплату
  // сотрудника с начала текущего месяца
  specification double getSalary()
  {
    post
    {
      // месяц может изменяться в пределах от 1 до 12
      tautology month(date) >= 1 && month(date) <= 12;

      // день может изменяться в пределах от 1 до 31
      tautology day(date) >= 1 && day(date) <= 31;

      // в апреле, июне, сентябре и ноябре 30 дней
      tautology (month(date) == 4 || month(date) == 6 ||
        month(date) == 9 || month(date) == 11) => day(date) <= 30;

      // в феврале не более 29 дней
      tautology month(date) == 2 => day(date) <= 29;

      // получаем текущее время
      long date = System.currentTimeMillis();

      switch(month(date))
      {
        case 1:
          mark "January";
          break;
        case 2:
          mark "February";
          break;
        ...
        case 12:
          mark "December";
          break;
      }

      // ситуация, когда номер месяца меньше 1 или больше 12
      // недостижима, эта связь указана в первом операторе tautology

      // в последний день длинного месяца дают премию
      if(day(date) == 31)
        mark "Bonus";

      // в феврале, апреле, июне, сентябре и ноябре меньше 31 дня,
      // для этих месяцев ситуация, когда день равен 31, недостижима,
      // эта связь указана в третьем и четвертом операторах tautology

      ...
    }
  }

  // объявление вспомогательных методов
  private int year(long date)
  {
    ...
  }

  private int month(long date)
  {
    ...
  }

  private int day(long date)
  {
    ...
  }
  ...
}

6. Связи между спецификациями

6.1. Наследование спецификационных классов

Назначение

Наследование спецификационных классов предназначено для обеспечения повторного использования инвариантов данных, аксиом, эквивалентностей и спецификационных методов, описанных в базовом спецификационном классе, в его подклассе (см. разделы Инварианты данных и Спецификационные методы соответственно).

Описание

Наследование спецификационных классов задается обычной конструкцией языка программирования Java extends.

Синтаксис

Super ::= "extends" ClassType ;

Семантические ограничения

  1. ClassType в Super должен быть спецификационным классом.
  2. При отсутствии Super данный спецификационный класс считается наследником класса jatva.lang.FormalModelObject.

6.2. Наследование спецификационных методов

Назначение

Описание

Язык JavaTESK предоставляет возможность повторного использования пред- и постусловий спецификационного метода (см. разделы Предусловие и Постусловие соответственно). Это достигается с помощью наследования функциональности спецификационного метода в совпадающем по сигнатуре спецификационном методе подкласса. Для этих целей служит конструкция refines super, которая должна располагаться в самом конце заголовка декларации спецификационного метода (см. раздел Спецификационные_методы).

Предусловие наследующего функциональность спецификационного метода есть дизъюнкция написанного предусловия и предусловия наследуемого метода, а постусловие – конъюнкция постусловия наследуемого метода и написанного постусловия. Пред- и постусловия вычисляются с учетом короткой логики.

SE_OperationRefinement
          ::=   SE_SuperOperationRefinement
              | SE_GeneralOperationRefinement
          ;

SE_SuperOperationRefinement
          ::= "refines" "super" ;

SE_GeneralOperationRefinement
          ::= "refines"
              (   ClassOrInterfaceType ( "up" Block )?
                | "up" Block
              )
              ( "down" Block )?
              ( "update" Block )?
          ;

Семантические ограничения

  1. Если присутствует конструкция SE_SuperOperationRefinement.
    1. В заголовке спецификационного метода она может встречаться не более одного раза.
    2. Данный спецификационный метод должен переопределять (override) по правилам языка Java некоторый спецификационный метод из базового класса.

Пример

// импортирование вспомогательного класса, реализующего список
import util.List;

// объявление спецификационного класса QueueSpecification,
// описывающего очередь 
specification class QueueSpecification
{
  // объявление поля items, хранящего элементы очереди
  public List items = new List();

  // объявление  спецификационного метода enq, описывающего
  // добавление элемента в конец  очереди
  specification void enq(Object obj)
  {
    // предусловие спецификационного  метода enq 
    pre
    {
      return obj != null;
    }

    // постусловие спецификационного метода enq 
    post
    {
       // выделяем ситуацию когда  очередь пуста
       if(items.isEmpty()) mark "Empty queue";
         // определяем  единственную ветвь функциональности
         branch "Single  branch";

       // проверяем, что  в конец очереди добавился элемент obj
       List new_items = (List)(pre items.clone());
       new_items.addLastElement(obj);

       return items.equals(new_items);
     }
  }
  ...
}

Ниже приводится фрагмент объявления спецификационного класса PQueueSpecification, наследующего спецификационный класс QueueSpecification.

// импортирование  вспомогательного класса, реализующего список
import util.List;

// объявление  спецификационного класса PQueueSpecification,
// описывающего  очередь с приоритетами и наследующего
// спецификационный класс QueueSpecification
specification class PQueueSpecification extends QueueSpecification
{
  // поле items, хранящее элементы очереди, наследуется от
  // спецификационного класса QueueSpecification

  // объявление поля priorities,  хранящего приоритеты элементов очереди
  public List priorities = new List();

  // объявление спецификационного метода enq, описывающего
  // добавление элемента с минимальным  приоритетом в очередь
  specification void enq(Object obj)
    refines super
    // указываем, что спецификационный метод enq наследует функциональность
    // спецификационного метода базового класса
  {
    // предусловие спецификационного метода enq
    pre
    {
      return obj != null;
    }
    // постусловие спецификационного метода enq
    post
    {
      // выделяем ситуацию когда очередь пуста
      if(items.isEmpty()) mark "Empty queue";

      // проверяем, что в конец списка приоритетов добавился
      // элемент new Priority(),  представляющий минимальный приоритет
      List new_priorities  = (List)(pre priorities.clone());
      new_priorities.addLastElement(new Priority());

      // проверку списка элементов писать не нужно, так как она
      // уже написана в постусловии  наследуемого метода
      return priorities.equalsByElement(new_priorities);
    }
 }
 ...
}

6.3. Уточнение спецификационных классов

// currently unsupported

Назначение

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

Описание

Уточнение спецификационных классов задается конструкцией refines с дополнительными элементами up и down, после которых указываются имена медиаторных классов (см. раздел Медиаторные классы).

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

Медиаторный класс, указанный после ключевого слова down, называется  уточняющим медиатором, он реализует уточняемый спецификационный класс через уточняющий.

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

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

Синтаксис

SE_ClassRefinement
          ::= "refines"
              ClassOrInterfaceType
              "up"
              ClassOrInterfaceType
              "down"
              ClassOrInterfaceType
          ;

Семантические ограничения

  1. Первый ClassOrInterfaceType должен быть спецификационным классом.
    Далее он называется уточняемым классом
  2. Второй ClassOrInterfaceType должен
    1. быть медиаторным классом,
    2. быть наследником данного класса
    3. иметь ровно одно реализационное поле (учитывая все унаследованные), тип которого должен совпадать с уточняемым классом или быть его предком, или базовым интерфейсом.
    Далее такой класс называется медиатором вверх.
  3. Третий ClassOrInterfaceType должен
    1. быть медиаторным классом,
    2. наследником уточняемого класса
    3. иметь ровно одно реализационное поле (учитывая все унаследованные), тип которого должен совпадать с данным классом или быть его предком, или базовым интерфейсом.
    Далее такой класс называется медиатором вниз.
  4. Запрещены циклические зависимости между спецификационными классами по отношению уточнения.
  5. Полные имена(без типовых параметров) всех медиаторов из SE_ClassRefinement данного класса должны быть различны.

Пример

// импортирование вспомогательного класса ArrayList
import java.util.ArrayList;

// объявление спецификационного класса ListSpecification
specification class ListSpecification
{
  // объявление спецификационной модели данных
  public ArrayList items;

  // объявление спецификационного метода add,
  // описывающего операцию, добавляющую элемент в список в i-ую позицию
  specification void add(Object obj, int i)
  {
    ...
  }

  // объявление спецификационного метода get,
  // описывающего операцию, возвращающую i-ый элемент списка
  specification Object get(int i)
  {
    ...
  }

  // объявление спецификационного метода remove,
  // описывающего операцию, удаляющую i-ый элемент списка и
  // возвращающую удаленный элемент
  specification Object remove(int i)
  {
    ...
  }

  // объявление спецификационного метода size,
  // описывающего операцию, возвращающую размер списка
  specification int size()
  {
    ...
  }
}

Ниже приводится объявление спецификационного класса StringListSpecification, описывающего список строк и уточняющего спецификационный класс ListSpecification. В нем присутствуют и другие спецификационные методы, но они переиспользуют уже имеющиеся спецификации.

// импортирование вспомогательного класса ArrayList
import java.util.ArrayList;

// объявление спецификационного класса StringListSpecification
specification class StringListSpecification
  // уточнение спецификационного класса ListSpecification
  refines ListSpecification
    up StringListToList
    down ListToStringList
{
  // объявление спецификационной модели данных
  public ArrayList items;

  // объявление инварианта данных AllItemsAreStrings, проверяющего,
  // что все элементы списка являются строками
  invariant AllItemsAreStrings()
  {
    for(int i = 0; i < items.size(); i++)
      if(!(items.get(i) instanceof String))
        return false;

    return true;
  }

  // объявление спецификационных методов
  specification void add(String obj, int i)
    refines StringListToList
  {
    post
    {
      return true;
    }
  }

  specification void addFirst(String obj)
    refines StringListToList
  {
    post
    {
      return true;
    }
  }

  specification void addLast(String obj)
    refines StringListToList
  {
    post
    {
      return true;
    }
  }

  specification String get(int i)
    refines StringListToList
  {
    post
    {
      return true;
    }
  }

  specification String remove(int i)
    refines StringListToList
  {
    post
    {
      return true;
    }
  }

  specification int size()
    refines StringListToList
  {
    post
    {
      return true;
    }
  }
}

Ниже приводится объявление обобщающего медиатора StringListToList.

// объявление обобщающего медиатора StringListToList
mediator class StringListToList implements StringListSpecification
{
  // объявление реализационной ссылки на объект
  // спецификационного класса ListSpecification
  implementation ListSpecification l;

  // объявление медиаторных методов
  mediator void add(String obj, int i)
  {
    l.add(obj, i);
  }

  mediator void addFirst(String obj)
  {
    l.add(obj, 0);
  }

  mediator void addLast(String obj)
  {
    l.add(obj, l.size());
  }

  mediator String get(int i)
  {
    return (String)l.get(i);
  }

  mediator String remove(int i)
  {
    return (String)l.remove(i);
  }

  mediator int size()
  {
    return l.size();
  }
}

Ниже приводится объявление уточняющего медиатора ListToStringList.

// импортирование вспомогательного класса ArrayList
import java.util.ArrayList;

// объявление уточняющего медиатора ListToStringList
mediator class ListToStringList implements ListSpecification
{
  // объявление реализационной ссылки на объект
  // спецификационного класса StringListSpecification
  implementation mediator StringListSpecification l;

  // объявление медиаторных методов
  mediator void add(Object obj, int i)
  {
    l.add((String)obj, i);
  }

  mediator Object get(int i)
  {
    return l.get(i);
  }

  mediator Object remove(int i)
  {
    return l.remove(i);
  }

  mediator int size()
  {
    return l.size();
  }

  // объявление блока синхронизации состояний
  update
  {
    items = (ArrayList)l.items.clone();
  }
}

7. Медиаторы

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

7.1. Медиаторные классы

Назначение

Медиаторные классы предназначены для реализации медиаторов.

Каждый медиаторный класс связывает спецификационный класс (см. раздел Спецификационные классы) с некоторым компонентом целевой системы или со спецификационным классом другого уровня абстракции.

Описание

Медиаторные классы помечаются модификатором mediator, который указывается в самом начале их объявления.

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

Методы медиаторного класса, перегружающие спецификационные методы, называются медиаторными методами.

При вызове у объекта медиаторного класса спецификационного метода, не перегруженного никаким медиаторным методом, будет выполнено тело спецификационного метода, если оно определено.

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

Синтаксис

SE_MediatorClassDeclaration
          ::= SE_ClassModifiers
              "mediator"
              UnmodifiedClassHeader
              SE_MediatorClassBody
          ;

UnmodifiedClassHeader
          ::= "class"
              Identifier
              ( TypeParameters )?
              ( Super )?
              ( Interfaces )?
          ;

SE_MediatorClassBody
          ::= "{"
              ( SE_MediatorClassBodyDeclaration )*
              "}"
          ;

SE_MediatorClassBodyDeclaration
          ::=   ClassBodyDeclaration
              | SE_ImplementationFieldDeclaration
              | SE_ImplementationInitializer
              | SE_MediatorMethodDeclaration
              | SE_ReactionMediatorMethodDeclaration
              | SE_UpdateBlockDeclaration
          ;

Семантические ограничения

  1. Должны выполняться общие семантические ограничения для всех видов специальных классов.
  2. Если в декларации медиаторного класса есть Super, то
    1. ClassType в Super должен быть медиаторным классом.
    2. В Interfaces не должно быть спецификационных классов, и может быть любое число интерфейсов,
  3. Иначе в Interfaces должен быть ровно один спецификационный класс, и может быть любое число интерфейсов.
  4. Если есть Super, то предком данного класса считается упомянутый в Super медиаторный класс, иначе - единственный упомянутый в Interfaces спецификационный класс.

Пример

// объявление  спецификационного класса AccountSpecification
specification class AccountSpecification
{
  static public int maximumCredit;

  public int balance;

  // объявление  спецификационного метода deposit
  specification void deposit(int sum)
  {
    ...
  }

  // объявление спецификационного  метода withdraw
  specification int withdraw(int sum)
  {
    ...
  }
  ...
}

Ниже приводится объявление медиаторного класса AccountMediator, наследующего спецификационный класс AccountSpecification и перегружающего спецификационные методы deposit и withdraw.

// импортирование  целевого класса Account 
import target.Account;
// объявление  медиаторного класса AccountMediator,
// наследующего  спецификационный класс AccountSpecification
mediator class AccountMediator implements AccountSpecification
{
  // реализационная ссылка на объект целевого класса
  implementation Account target = new Account();

  // объявление медиаторного метода deposit
  mediator void deposit(int sum)
  {
    // вызов целевого метода deposit
    target.deposit(sum);
  }

  // объявление медиаторного метода withdraw
  mediator int withdraw(int sum)
  {
    // вызов целевого метода withdraw
    return target.withdraw(sum);
  }

  // объявление блока синхронизаций состояний объектов
  update
  {
    balance = target.balance;
  }

  // объявление блока синхронизаций состояний классов
  static update
  {
     maximumCredit = target.maximumCredit;
  }
}

7.2. Реализационные ссылки

Назначение

Реализационные ссылки являются специальными полями медиаторных классов (см. раздел Медиаторные классы) и предназначены для хранения ссылок на объекты целевой системы.

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

Описание

Объявление реализационных ссылок отличается от объявления обычных полей добавлением в начало объявления модификатора implementation.

Синтаксис

SE_ImplementationFieldDeclaration
          ::= ( Annotation )*
              "implementation"
              UnmodifiedFieldDeclaration
          ;

UnmodifiedFieldDeclaration
          ::= Type
              VariableDeclarators
              ";"
          ;

Семантические ограничения

  1. Пространство имен обычных полей и реализационных полей одно.
  2. Реализационное поле должно быть ссылочного типа.
  3. Ограничения на использования типов и инициализаторов те же, что и для обычных полей в Java.
  4. Реализационное поле не может перекрывать (совпадать по имени) обычное поле, декларированное в предках.
  5. Реализационное поле не может перекрывать реализационное поле, декларированное в предках.
  6. Обычное поле не должно перекрывать реализационное поле предка.
  7. Реализационное поле имеет доступность public.

Пример

// импортирование целевого класса Queue
import target.Queue;

// объявление медиаторного класса QueueMediator,
// наследующего спецификационный класс QueueSpecification
mediator class QueueMediator implements QueueSpecification
{
  // объявление реализационной ссылки targetObject
  // на объект целевого класса Queue
  implementation Queue targetObject = null;

  ...
}

7.3. Медиаторные методы

Назначение

Описание

Синтаксис

SE_MediatorMethodDeclaration
          ::= (   SE_CommonModifier
                | "synchronized"
                | "final"
              )*
              "mediator"
              MethodHeader
              MethodBody
          ;

Семантические ограничения

  1. Должны выполняться общие семантические требования к специальным методам.
  2. Медиаторный метод должен перегружать (override) по правилам Java некоторый метод из класса, являющимся предком медиаторного класса, в котором декларирован данный метод.
    Такой метод назовем перегружаемым методом.
    В отличии от Java, здесь понятие перегрузки включает в себя и статические методы.
  3. Требования соответсвия данного метода и перегружаемого метода такие же, как в Java.
  4. Если MethodHeader медиаторного метода содержит Throws-часть, то требования к ней такие же, как и к Throws-части спецификационных методов (см. здесь).
  5. Дополнительные требования:
    1. данный метод должен быть static или не static одновременно с перегружаемым методом.
    2. перегружаемый метод должен быть либо медиаторным методом, либо спецификационным методом.

Пример



7.4. Оператор обращения к реализации

Назначение

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

Описание

Синтаксис

SE_ImplementationStatement
          ::= "implementation"
              ( Throws )?              
              Statement
          ;

Семантические ограничения

  1. Оператор implementation может находиться только внутри медиаторных методов.
  2. Оператор implementation не должен быть вложен в другой оператор implementation или в оператор update.
  3. Если оператор implementation содержит Throws-часть, то ...
    1. требования к ней такие же, как и к Throws-части спецификационных методов (см. здесь);
    2. считается, что исключения всех типов, перечисленных в Throws-части (или наследников этих типов), не выходят за рамки оператора implementation. (Как будто опертор обёрнут в TryStatement с catch-частями, ловящями исключения этих типов);
    3. ошибкой считается следующая ситуация: в Throws-части присутствует checked-исключение E такое, что вложенный Statement никогда не выбрасывает исключение типа E или типа - наследника Е.

Пример



7.5. Оператор синхронизации состояния модели и целевой системы

Назначение

Описание

Синтаксис

SE_UpdateStatement
          ::= "update"
              (
                "["
                ( SE_InvocationResultStorage )?
                MethodInvocation
                "]"
              )?
              Statement
          ;

Семантические ограничения

  1. Оператор update может находиться только внутри
    1. медиаторных методов,
    2. методов-медиаторов реакций
    3. в постусловии спецификационной операции
  2. Оператор update может содержать квадратные скобки тогда и только тогда, когда он находится внутри метода-медиатора реакции.
  3. Оператор update не должен быть вложен в другой оператор update или в оператор implementation.
  4. Если в постусловии спецификационного метода есть операторы branch, оператор update должен следовать после некоторого оператора branch по потоку управления. Считается, что оператор update, вложенный в оператор branch, следует за последним по потоку управления.
  5. Если в медиаторном методе есть операторы implementation, то оператор update не должен предшествовать по потоку управления ни какому из операторов implementation.
  6. В операторе update не должно быть необработанных ThrowStatement.
  7. Параметры объемлющего метода и переменные, декларированные вне оператора update, рассматриваются в нём как final.
  8. Если оператор update содержит квадратные скобки, то
    1. Метод, вызываемый в MethodInvocation, должен быть спецификацией реакции.
    2. Если в MethodInvocation вызов метода производится через объект, то этот объект должен быть получен посредством конструкций this или super, или через квалифицированный идентификатор.
    3. Если SE_InvocationResultStorage имеет вид SE_VariableDeclarationResultStorage, то декларированная переменная ...
      1. входит в область видимости, объемлющую оператор update;
      2. не должна использоваться внутри Statement, вложенного в оператор update.

Пример



7.6. Блок синхронизации состояния модели и целевой системы

Назначение

Описание

Синтаксис

SE_UpdateBlockDeclaration
         ::= (   "static"
               | "strictfp"
               | Annotation
               )*
             "update"
             Block
         ;

Семантические ограничения

  1. Допустим только в медиаторном классе или в классе-медиаторе реакций.
  2. В классе не должно содержаться более одного статического и одного нестатического блоков update.
  3. Блок update имеет ту же семантику, что и обычный void метод Java без параметров, статический или нестатический в зависимости от наличия модификатора static.
  4. Дополнительное требование: в блоке update не должно быть необработанных ThrowStatement.

Пример



7.7. Реализационно-зависимые инициализаторы

Назначение

Реализационно-зависимые инициализаторы предназначены для описания действий, инициализирующих объекты целевой системы, доступные через реализационные ссылки (см. раздел Реализационные ссылки) медиаторных классов (см. раздел Медиаторные классы).

Описание

Реализационно-зависимые инициализаторы объявляются в медиаторных классах в блоках инициализации, помеченных ключевым словом implementation.

Выполнение реализационно-зависимых инициализаторов, объявленных в одном медиаторном классе, осуществляется в статическом методе create этого класса после создания объекта медиаторного класса и присваивания значений параметров метода реализационным ссылкам созданного объекта. Порядок выполнения инициализаторов совпадает с порядком их объявления.

Синтаксис

SE_ImplementationInitializer
          ::= "implementation" Block ;

Семантические ограничения

  1. Ограничения на содержимое блока те же, что и для инициализаторов в Java, за исключением п. 2.
  2. Несмотря на положение такого инициализатора в коде в нем доступны все декларированные в классе и соответствующие по контексту (в static - только static поля, в не static - все) поля, в том числе реализационные.

Пример

Ниже приводится фрагмент объявления целевого класса Account.

// объявление пакета target
package target;

// объявление целевого класса Account
public class Account
{
  // объявление поля balance
  public int balance;

  ...
}

Ниже приводится фрагмент объявления медиаторного класса AccountMediator, наследующего спецификационный класс AccountSpecification (см. раздел Медиаторные классы) и осуществляющего инициализацию поля balance объекта целевого класса Account в реализационно-зависимом инициализаторе.

// импортирование целевого класса
import target.Account;

// объявление медиаторного класса AccountMediator
mediator class AccountMediator implements AccountSpecification
{
  // реализационная ссылка на объект целевого класса
  implementation Account targetObject = null;

  // реализационно-зависимый инициализатор
  implementation
  {
    targetObject.balance = 0;
  }

  ...
}

8. Тестовые сценарии

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

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

8.1. Сценарные классы

Назначение

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

Описание

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

Внутри сценарных классов могут находиться объявления полей данных и методов.

Сценарные классы являются неявными наследниками класса jatva.lang.FormalScenario, который в свою очередь наследуется от java.lang.Thread, поэтому их можно использовать в качестве потоков: вызов метода start() в объекте сценарного класса запускает в отдельном потоке выполнение соответсвующего сценария.

В классе jatva.lang.FormalScenario определены следующие методы:

В сценарном классе могут объявляться сценарные методы и блок вычисления обобщённого состояния (см. разделы Сценарные методы и Блок вычисления обобщённого состояния).

Синтаксис

SE_ScenarioClassDeclaration
          ::= SE_ClassModifiers
              "scenario"
              UnmodifiedClassHeader
              SE_ScenarioClassBody
          ;

UnmodifiedClassHeader
          ::= "class"
              Identifier
              ( TypeParameters )?
              ( Super )?
              ( Interfaces )?
          ;

SE_ScenarioClassBody
          ::= "{"
              ( SE_ScenarioClassBodyDeclaration )*
              "}"
          ;

SE_ScenarioClassBodyDeclaration
          ::=   ClassBodyDeclaration
              | SE_ScenarioMethodDeclaration
              | SE_ScenarioStateBlock
          ;

Семантические ограничения

  1. Должны выполняться общие семантические ограничения для всех видов специальных классов.
  2. ClassType в Super должен быть сценарным классом
  3. При отсутствии Super данный класс считается наследником класса jatva.lang.FormalScenario

Пример

// объявление спецификационного класса SqrtSpecification
specification class SqrtSpecification
{
  // объявление статического спецификационного метода sqrt,
  // описывающего извлечение квадратного корня
  specification double sqrt(double x)
  {
    ...
  }
}

Ниже приводится объявление сценарного класса SqrtScenario, использующего спецификационный класс SqrtSpecification.

// объявление сценарного класса SqrtScenario
scenario class SqrtScenario
{
  public double largeNumber = 1.0e15D;
  public int numberOfTrials = 5;

  // объявление конструкора сценарного класса SqrtScenario
  public SqrtScenario(int p_number)
  {
    SqrtSpecification.attachClassOracle();
    numberOfTrials = p_number;
  }

  // объявление сценарного метода sqrt
  scenario sqrt()
  {
    iterate(int i = 0; i < numberOfTrials; i++)
    {
      choice
      {
        { execute SqrtSpecification.sqrt(i/largeNumber/numberOfTrials); }
        { execute SqrtSpecification.sqrt(1+i*largeNumber/numberOfTrials); }
        { execute SqrtSpecification.sqrt(((1 << i)+1)* largeNumber); }
      }
    }

    return true;
  }

  // объявление статического метода main, запускающего тест
  public static void main(String args[])
  {
    SqrtScenario myscenario = new SqrtScenario(5);

    // запускаем выполнение соответствующего теста в отдельном потоке
    myscenario.start();

    try
    {
       // ожидаем окончание выполнение теста
       myscenario.join();
    }
    catch(InterruptedException error)
    {
    }
  }
}

8.2. Сценарные методы

Назначение

Сценарные методы предназначены для описания набора обращений к целевой системе, которые необходимо осуществить в каждом обобщенном состоянии, достижимом во время тестирования (см. раздел Обобщенное состояние документа JavaTESK. Руководство пользователя).

Они также могут производить проверку корректности поведения вызываемых операций целевой системы.

Описание

Сценарные методы представляются как методы сценарного класса, не имеющие параметров, помеченные модификатором scenario и возвращающие результат типа boolean: true – если проверка показывает, что целевая система ведет себя корректно, false – иначе.

В сценарных методах для описания набора обращений к целевой системе можно использовать следующие конструкции:

Синтаксис

SE_ScenarioMethodDeclaration
          ::= (   "strictfp"
                | "final"
                | Annotation
              )*
              "scenario"
              Identifier
              "("
              ")"
              Block
          ;

Семантические ограничения

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

  1. Должны выполняться общие семантические требования к специальным методам.
  2. Перегрузка (overriding) сценарных методов при наследовани.
    Сценарный метод
    1. может перегружать только другой сценарный метод
    2. и не может реализовывать методы, наследуемые от интерфейсов
  3. Если данной сценарный метод перегружает некоторый сценарный метод из одного из классов-предков, то этот метод будем называть методом-предком.
    1. Метод-предок не должен быть final
  4. Сценарный метод нельзя вызывать

Пример

  // объявление сценарного метода sqrt
  scenario sqrt()
  {
    iterate(int i = 0; i < numberOfTrials; i++)
    {
      choice
      {
        { SqrtSpecification.sqrt(i/largeNumber/numberOfTrials); }
        { SqrtSpecification.sqrt(1+i*largeNumber/numberOfTrials); }
        { SqrtSpecification.sqrt(((1 << i)+1)* largeNumber); }
      }
    }

    return true;
  }

8.3. Оператор execute

Назначение

Описание

Синтаксис

SE_ExecuteStatement
         ::= "execute" Statement ;

Семантические ограничения

  1. Оператор execute может находиться только в сценарном методе
  2. Оператор execute не может быть вложен в другой оператор execute

Пример

// объявление  сценарного класса BarrierTestScenario 
scenario class BarrierTestScenario
{
  public BarrierSpecification barrier;

  // объявление сценарного метода deposit
  scenario waitCall()
  {
    if( barrier.awaitedThreads > 0 )
    {
      // старт тестового фрема execute
      barrier.waitCall();
    }
    return true;
  }
}

8.4. Итерационные циклы

Назначение

Итерационные циклы предназначены для записи перебора значений параметров обращений к операциям целевой системы в сценарных методах (см. раздел Сценарные методы).

Описание

Синтаксис итерационного цикла языка JavaTESK похож на синтаксис цикла for(;;) языка Java. Объявление начинается с ключевого слова iterate, после которого в скобках через ; указаны:

Все указанные части, за исключением первой, являются необязательными и могут быть опущены. Объявление заканчивается телом итерационного цикла – итерационным блоком. Несмотря на сходство в записи итерационного цикла и цикла for(;;), итерационные циклы всегда выполняются хотя бы один раз, если выполнено условие фильтрации.

Итерационный цикл:

iterate(<объявление и инициализация итерационной переменной>
       ;<условие продолжение итерации>
       ;<шаг итерации>
       ;<условие фильтрации>
       )
  <итерационный блок>

подобен циклу:

<объявление и инициализация итерационной переменной>;
do
{
  if(<условие фильтрации>)
    <итерационный блок>;
  <шаг итерации>;
}
while(<условие продолжение итерации>);

Итерационные переменные, то есть переменные, значения которых перебираются в итерационном цикле, не являются обычными переменными, значения которых теряются при выходе из сценарного метода. Их значения запоминаются в специальной структуре данных, связанной с текущим обобщенным состоянием (см. раздел Обобщенное состояние документа J@T. Руководство пользователя). Эти значения становятся доступными, как только тест перейдет в данное обобщенное состояние снова.

Итерационные переменные являются частным случаем переменных обобщенного состояния (см. раздел Переменные обобщенного состояния).

Синтаксис

SE_Iterate
          ::= "iterate"
              "("
              Type
              VariableDeclarator
              ";"
              Expression
              ";"
              ( ForUpdate )?
              ( ";" ( Expression )? )?
              ")"
              Statement
          ;

ForUpdate ::= StatementExpressionList ;

StatementExpressionList
         ::= StatementExpression
             ( "," StatementExpression )*
         ;

Семантические ограничения

  1. Тип переменной, декларируемой в декларационной части, должен быть доступен в данном месте по правилам Java.
  2. Тип такой переменной должен быть типом значения или реализовывать интерфейс java.lang.Cloneable
  3. VariableDeclarator должен содержать инициализацию VariableInitializer
  4. Оба выражения в заголовке SE_Iterate должны иметь тип boolean или java.lang.Boolean (второй вариант возможен в Java5)
  5. Поток данных в SE_Iterate рассматривается аналогично BasicForStatement
  6. SE_Iterate не должен использоваться внутри WhileStatement, DoStatement или ForStatement
  7. Декларируемая итерационная переменная рассматривается как final внутри То есть в этих контекстах переменная не должна использоваться в левой части присваивания, а также не должна использоваться с операторами ++, --.
  8. Запрещены continue, относящиеся к SE_Iterate. Запрещены continue на метки, стоящие непосредственно перед SE_Iterate.

Пример

// объявление сценарного класса AccountTestScenario
scenario class AccountTestScenario
{
  // объявление сценарного метода deposit
  scenario deposit()
  {
    if(target.balance < 10)
    {
      // итерационный цикл, описывающий перебор трех вызовов:
      // target.deposit(1), target.deposit(3) и target.deposit(5)
      iterate(int i = 1; i < 6; i++; i % 2 == 1)
      {
        // вызов целевого метода deposit
        target.deposit(i);
      }
    }

    // дополнительных проверок не производится
    return true;
  }
  ...
}

8.5. Итерационные циклы по коллекции

Назначение

Описание

Синтаксис

SE_CollectionIterate
          ::= "iterate"
              "("
              Type
              Identifier
              ":"
              Expression
              ( ";" ( Expression )? )?
              ")"
              Statement
          ;

Семантические ограничения

  1. Такая конструкция должна удовлетворять правилам Java аналогичным требованиям к конструкции EnhancedForStatement - тип первого выражения должен быть
  2. Дополнительное требование к типу первого выражения: если этот тип не является массивом, то он должен реализовывать интерфейс java.lang.Cloneable
  3. Выражение-фильтр может иметь тип boolean или java.lang.Boolean (второй вариант возможен только в Java5)
  4. Декларируемая итерационная переменная рассматривается как final внутри
  5. Поток данных в SE_CollectionIterate рассматривается аналогично EnhancedForStatement
  6. SE_CollectionIterate не должен использоваться внутри WhileStatement, DoStatement или ForStatement
  7. Запрещены continue, относящиеся к SE_CollectionIterate. Запрещены continue на метки, стоящие непосредственно перед SE_CollectionIterate.

Пример

// объявление сценарного класса StoreTestScenario
scenario class StoreTestScenario
{
  // ссылка на модель товарного склада
  public StoreSpecification target;

  // список наименований товаров
  public ArrayList<String> goods;

  // максимальное количество едениц товара в каждом заказе 
  public int maxAmountToOrder = 5;

  scenario order()
  {
    iterate(String goodsName : goods)
    {
      iterate(int amount = 1; amount<=maxAmountToOrder; amount++)
      {
        target.order(goodsName, amount);
      }
    }
  }
}

8.6. Итерационные циклы по элементам покрытия

Назначение

Описание

Синтаксис

SE_CoverageIterate
          ::= ( "casual" )? // not supported yet
              "coverage"
              (
                "["
                SE_CoverageSpecifier
                "]"
              )?
              ":"
              (
                  SE_Iterate
                | SE_CollectionIterate
              )
          ;

SE_CoverageSpecifier
          ::=   SE_IntegralTypeCoverageSpecifier
              | SE_ExpressionCoverageSpecifier
          ;

SE_IntegralTypeCoverageSpecifier
           ::= Expression "," Expression ;

SE_ExpressionCoverageSpecifier
           ::= Expression ;

Семантические ограничения

Определение: iterate-блоки, вложенные в SE_CoverageIterate и содержащие в себе инструкцию фильтрации, называются нанизанными.

Нанизанные iterate-блоки образуют цепочку от coverage iterate до инструкцию фильтрации.

  1. SE_CoverageIterate обязательно должен содержать внутри инструкцию фильтрации.
  2. SE_CoverageIterate может содержать не более одной инструкции фильтрации.
  3. Если в качестве SE_CoverageSpecifier используется SE_IntegralTypeCoverageSpecifier, то содержащаяся в данном блоке инструкция фильтрации должна быть SE_ExpressionFilterStatement.
  4. Если в качестве SE_CoverageSpecifier используется SE_ExpressionCoverageSpecifier, то содержащаяся в данном блоке инструкция фильтрации должна быть SE_MethodInvocationFilterStatement.
  5. Для каждого нанизанного iterate-блока IBi на всех путях от начала его тела должны быть достижимы
  6. Оба выражения в SE_IntegralTypeCoverageSpecifier должны иметь тип, неявно приводимый к long.
  7. Выражение в SE_ExpressionCoverageSpecifier должно иметь тип java.lang.String.
  8. Для нанизанных iterate-ов должны все требования, к ним относящиеся, кроме

Пример



8.7. Инструкция фильтрации по целочисленным значениям

Назначение

Описание

Синтаксис

SE_ExpressionFilterStatement
          ::= "filter"
              "["
              Expression
              "]"
              ";"
          ;

Семантические ограничения

  1. Может находиться только внутри SE_CoverageIterate
  2. Не должна входить внутри SE_CoverageIterate в блоки try, catch или finally
  3. Тип содержащегося выражения должен быть неявно приводим к long.
  4. SE_ExpressionFilterStatement не должен использоваться внутри WhileStatement, DoStatement или ForStatement

Пример



8.8. Инструкция фильтрации по покрытию метода

Назначение

Описание

Синтаксис

SE_MethodInvocationFilterStatement
          ::= "filter"
              ":"
              ( SE_InvocationResultStorage )?
              MethodInvocation
              ";"
          ;

SE_InvocationResultStorage
          ::=   SE_VariableDeclarationResultStorage
              | SE_AssignmentResultStorage
          ;

SE_VariableDeclarationResultStorage
          ::= VariableModifiers
              Type
              VariableDeclaratorId
              "="
          ;

SE_AssignmentResultStorage
          ::= ExpressionName
              AssignmentOperator
          ;

Семантические ограничения

  1. Может находиться только внутри SE_CoverageIterate
  2. Не должен входить внутри SE_CoverageIterate в блоки try, catch или finally
  3. Если в MethodInvocation слева от имени метода (или слева от типовых аргументов) находится конструкция, определяющая объект (не класс!), то эта конструкция должна быть квалифицированным идентификатором.
  4. Вызываемый в MethodInvocation метод должен быть спецификационным или медиаторным методом.
  5. SE_MethodInvocationFilterStatement не должен использоваться внутри WhileStatement, DoStatement или ForStatement

Пример

// объявление сценарного класса IntSetTestScenario
scenario class IntSetTestScenario
{
  // ссылка на модель множества целых чисел
  public IntSetSpecification objectUnderTest;

  // максимальное количество элементов в целевом множестве
  public int maximumNumberOfItems = 5;

  // объявление сценарного метода remove
  scenario remove()
  {
    // начало итерации данных
    // фильтрация будет производиться по покрытию маркированных путей
    coverage["mark"]:
    iterate(int i = 0; i < maximumNumberOfItems; i++)
    {
      // инструкция фильтрации по элементам покрытия метода remove
      filter: objectUnderTest.remove(i);
    }
    return true;
  }
}

8.9. Комбинатор выбора

Назначение

Комбинатор выбора предназначен для описания перебора цепочек инструкций из некоторого множества в сценарных методах (см. раздел Сценарные методы).

Описание

Объявление комбинатора выбора начинается с ключевого слова choice, после которого в фигурных скобках указывается список, возможно пустой, блоков, каждый из которых содержит некоторую цепочку инструкций.

Комбинатор выбора:

choice
{
  { <цепочка1> }
  { <цепочка2> }
}

эквивалентен следующему итератору:

iterate(int i = 0; i < 2; i++)
{
  switch(i)
  {
    case 0:
      { <цепочка1> }
      break;
    default:
      { <цепочка2> }
      break;
  }
}

Синтаксис

SE_Choice
          ::= "choice"
              "{"
              ( Block )*
              "}"
          ;

Семантические ограничения

  1. Комбинатор выбора может использоваться только в сценарных методах (см. раздел Сценарные методы).
  2. Количество блоков в SE_Choice должно быть не меньше 2-х.
  3. Поток данных в SE_Choice устроен так же, как в switch, в котором
    1. все указнные блоки помечены разными метками и отделены друг от друга с помощью break
    2. есть метка default (соответствует последнему из блоков SE_Choice)
  4. SE_Choice не должен использоваться внутри WhileStatement, DoStatement или ForStatement
  5. SE_Choice не должен быть вложен в SE_CoverageIterate и одновременно содержать в себе инструкцию фильтрации.

Пример

// объявление  сценарного класса AccountTestScenario 
scenario class AccountTestScenario
{
  public AccountSpecification target;

  // объявление сценарного метода deposit 
  scenario deposit()
  {
    if(target.balance < 10)
    {
      // комбинатор выбора, описывающего  перебор двух вызовов:
      //  target.deposit(1) и target.deposit(5)
      choice
      {
        { target.deposit(1); }
        { target.deposit(5); }
      }
    }

    // дополнительных проверок не производится
    return true;
  }

  // объявление сценарного метода withdraw
  scenario withdraw()
  {
    // комбинатор выбора, описывающего перебор  двух вызовов:
    // target.withdraw(1) и target.withdraw(5)
    choice
    {
      { target.withdraw(1); }
      { target.withdraw(5); }
    }

    // дополнительных проверок не производится
    return true;
  }
  ...
}

8.10. Комбинатор сериализации

Назначение

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

Описание

Объявление комбинатора сериализации начинается с ключевого слова serialize, после которого в фигурных скобках указывается список, возможно пустой, блоков, каждый из которых содержит некоторую цепочку операций.

Комбинатор сериализации:

serialize
{
  { <инструкция1>;  <инструкция2>; }
  { <инструкция3>;  <инструкция4>; }
}

эквивалентен следующему комбинатору выбора:

choice
{
  { <инструкция1>; <инструкция2>; <инструкция3>; <инструкция4>;  }
  { <инструкция3>; <инструкция4>; <инструкция1>; <инструкция2>;  }
  { <инструкция1>; <инструкция3>; <инструкция4>; <инструкция2>;  }
  { <инструкция3>; <инструкция1>; <инструкция2>; <инструкция4>;  }
  { <инструкция1>; <инструкция3>; <инструкция2>; <инструкция4>;  }
  { <инструкция3>; <инструкция1>; <инструкция4>; <инструкция2>;  }
}

Блок { <цепочка инструкций> } внутри цепочки комбинатора сериализации рассматривается как одна инструкция.

Синтаксис

SE_Serialize
          ::= "serialize"
              "{"
              ( Block )*
              "}"
          ;

Семантические ограничения

  1. Комбинатор сериализации может использоваться только в сценарных методах (см. раздел Сценарные методы).
  2. Количество блоков в SE_Serialize должно быть не меньше 2-х.
  3. Все локальные переменные, декларированные непосредственно внутри блоков верхнего уровня, входящих в данный оператор, в том числе и в SE_StateVariableDeclaration, должны иметь различные идентификаторы.
    Иначе при сериализации этих деклараций они будут путаться.
  4. Операторы break, расположенные внутри SE_Serialize, должны относиться только к конструкциям, также расположенным внутри SE_Serialize.
  5. От начала каждого Block-а должна быть достижима его конечная точка.
  6. Не допускается использование внутри одного из блоков необработанных ThrowStatement
  7. SE_Serialize не должен использоваться внутри WhileStatement, DoStatement или ForStatement
  8. SE_Serialize не должен быть вложен в SE_CoverageIterate и одновременно содержать в себе инструкцию фильтрации.

Пример

// объявление сценарного метода addAndRemove
scenario addAndRemove()
{
  // объявление комбинатора сериализации
  serialize
  {
    // описание множества цепочек для сериализации
    { target.add(1); target.add(1); target.remove(1); }
    { target.remove(2); target.add(2); target.remove(2); }
  }

  // дополнительных проверок не производится
  return true;
}

8.11. Переменные обобщенного состояния

Назначение

Переменные обобщенного состояния предназначены для хранения данных, связанных с обобщенным состоянием (см. раздел Обобщенное состояние документа J@T. Руководство пользователя). Их значения запоминаются в специальной структуре данных, связанной с текущим обобщенным состоянием, и становятся доступными, как только тест перейдет в него снова.

Описание

Объявление переменных обобщенного состояния начинается с модификатора state, после которого следует объявление переменных.

Код вида:

<оператор1>;
state int i = 0;
<оператор2>;

эквивалентен следующему (см. раздел Итерационные циклы):

<оператор1>;
iterate(int i = 0;;)
{
  <оператор2>;
  break;
}

Синтаксис

SE_StateVariableDeclaration
          ::= "state"
              Type
              VariableDeclarators
              ";"
          ;

Семантические ограничения

  1. Переменные обобщенного состояния могут использоваться только в сценарных методах
  2. Тип переменной должен быть доступен в данном месте по правилам Java. Кроме того, он не может быть вложенным (возможно, косвенно) во вложенный в данный private класс или сам являться таковым.
  3. Тип такой переменной должен быть типом значения или реализовывать интерфейс java.lang.Cloneable

Пример

// объявление сценарного метода fibbonachi 
scenario fibbonachi()
{
  // объявление переменных обобщенного состояния
  state int f1 = 0;
  state int f2 = 1;

  // итерационный цикл, генерирующий последовательность из
  // первых десяти чисел Фиббоначи
  iterate(int i = 1; i <= 10; i++)
  {
    f2 = f2 + f1;
    f1 = f2 – f1;

    target.method(f1);
  }

  return true;
}

8.12. Блок вычисления обобщённого состояния

Назначение

Описание

Синтаксис

SE_ScenarioStateBlock
          ::= (   "strictfp"
                | Annotation
              )*
              "state"
              Block
          ;

Семантические ограничения

  1. Может использоваться только внутри декларации сценарного класса
  2. Каждая декларация сценарного класса должна содержать не более одного SE_ScenarioStateBlock
  3. Ограничения на содержимое блока те же, что и для не static метода без параметров, возвращающего значение типа java.lang.Object
    1. Несмотря на положение такого блока в коде в нем доступны все декларированные в классе поля.
    2. Блок должен возвращать значение типа Object или выбрасывать исключение unchecked типа.
    3. В блоке могут использоваться конструкции super и this.

Пример

// объявление сценарного класса AccountTestScenario 
scenario class AccountTestScenario
{
  public AccountSpecification target;

  // объявление блока вычисления обобщённого состояния
  state
  {
    return new Integer( target.balance );
  }

  ...
}

8.13. Инструкция контролируемого вызова спецификационного метода

Назначение

Описание

Синтаксис

SE_CheckedPreStatement
          ::= "pre"
              "["
              ( SE_InvocationResultStorage )?
              MethodInvocation
              "]"
              (   ( ":" Statement )
                | ";"
              )
          ;

Семантика

  1. Метод, вызываемый в MethodInvocation, должен быть или медиаторным, или спецификационным.
  2. Если в MethodInvocation слева от имени метода (или слева от типовых аргументов) находится конструкция, определяющая объект (не класс!), то эта конструкция должна быть квалифицированным идентификатором.
  3. Если SE_InvocationResultStorage имеет вид SE_VariableDeclarationResultStorage, то область видимости декларированной переменной - вложенный Statement.
  4. Следующие типы должны быть синтаксически выразимы, то есть не должны быть wildcard-ами или массивами wildcard-ов:
    1. типы всех выражений-аргументов MethodInvocation;
    2. тип левой части MethodInvocation (из 1 и 2 следует, что она должна быть), если вызов производится через объект, а не через тип.
    Рассматриваемые здесь типы - это типы актуализированного метода из MethodInvocation.

Пример

Спецификации реакций

Назначение

Спецификации реакций предназначены для описания обратного интерфейса /*to change*/ теструемых компонентов. Обратный интерфейс /*to change*/ компонента - это набор операций, наличия которых он требует в своем окружении, и которые он может вызывать в ходе своей работы. Обратный интерфейс /*to change*/ состоит из реакций. Спецификация реакции определяет требования к функциональности этой реакции со стороны компонента, использующего его.

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

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

Описание

Спецификация реакции, так же, как и спецификация обычного метода, состоит из общего блока, пред- и постусловия. Выделяется она ключевыми словами reaction specification.

Синтаксис

SE_ReactionSpecification
          ::= (   SE_CommonModifier
                | "synchronized"
              )*
              "reaction"
              "specification"
              SE_MethodHeader
              SE_MethodBody
          ;

Семантические ограничения

  1. Должны выполняться общие семантические требования к специальным методам.
  2. Тип возращаемого значения должен быть void
    // временное ограничение, сейчас мы поддерживаем только void.
  3. Для спецификаций реакций по аналогии должны выполняться требования из следующих разделов, относящихся к обычным спецификационным методам:
    1. 4.3. Спецификационные методы
    2. 4.4. Описание возможных исключений
    3. 6.2. Наследование спецификационных методов
    "По аналогии" означает, что при применении требований, изложенных в перечисленных разделах, к спецификациям реакций, следует вместо "спецификационный метод" читать "спецификация реакции".

Пример



Классы-медиаторы реакций

Назначение

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

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

Помимо обращений к реакциям класс-медиатор реакций может определять зависящие от тестируемой реализации действия по синхронизации модельного состояния.

Описание

Синтаксис

SE_ReactionMediatorClassDeclaration
         ::= SE_ClassModifiers
             "reaction"
             "mediator"
             UnmodifiedClassHeader
             SE_ReactionMediatorClassBody
         ;

SE_ReactionMediatorClassBody
         ::= "{"
             ( SE_ReactionMediatorClassBodyDeclaration )*
             "}"
         ;

SE_ReactionMediatorClassBodyDeclaration
         ::=   ClassBodyDeclaration
             | SE_ReactionMediatorMethodDeclaration
             | SE_UpdateBlockDeclaration
         ;

Семантические ограничения

  1. Должны выполняться общие семантические ограничения для всех видов специальных классов.
  2. Класс-медиатор реакций может быть унаследован только либо от обычного Java-класса, либо от другого класса-медиатора реакций.

Пример



Методы-медиаторы реакций

Назначение

Описание

Синтаксис

SE_ReactionMediatorMethodDeclaration
          ::= (   SE_CommonModifier
                | "synchronized"
                | "final"
              )*
              "reaction"
              "mediator"
              MethodHeader
              MethodBody
          ;

Семантические ограничения

  1. Может находиться либо в медиаторном классе, либо в классе-медиаторе реакций.
  2. Должны выполняться общие семантические требования к специальным методам.
  3. Если метод-медиатор реакций перегружает (overrides) один из методов в предках данного класса или в реализуемых им интерфейсах, то этот базовый перегруженный метод должен быть ...

Пример

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


ChatSpecification cs;

reaction mediator void acceptMessage( ChatMessage m )
{
   if( m instanceof ErrorMessage )
      cs.errorSend( m.getDestination(), m.getErrorCode() );
   else
   if( m instanceof TextMessage )
      cs.textSend( m.getDestination(), m.getText() );
}

Использование каналов регистрации реакций

Описание

Назначение

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

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

Синтаксис

SE_ChannelizeEventsStatement
          ::= "<:"
              Expression
              "default"
              ">:"
              Block
          ;

Семантические ограничения

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

Пример

// метод-медиатор реакций для сервера чата, обладающим единственным
// каналом передачи выходных сообщений
// все сообщения, посланные сервером чата, воспринимаются одним и тем же потоком


ChatSpecification cs;

reaction mediator void sendMessage( byte[] chatMessageData )
{
   ChatMessage cm = Utils.parseChatMessage( chatMessageData );
   <: cm.getClientID() :>
   {
      // вызов обратного медиаторного метода из
      // раздела Методы-медиаторы реакций
      acceptMessage( cm );
   }
}

Пользовательские комментарии к вердиктам

Описание

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

Назначение

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

Вердикт задаётся первым выражением, а комментарий, вычисляемый в случае отрицательного вердикта, - во втором.

Синтаксис

SE_ReturnStatement 
          ::= "return"
              Expression
              ":"
              Expression
              ";"
          ;

Семантические требования

  1. SE_ReturnStatement может использоваться только в сценарных методах, инвариантах, а также в пред- и постусловиях спецификационных операций.
  2. Первое выражение рассматривается как, собственно, возвращаемое значение и должно удовлетворять требованиям к возвращаемым значениям той языковой конструкции, в которой используется SE_ReturnStatement.
  3. Тип второго выражения должен быть отличен от void.

Пример

    reaction specification void sendNotSubscribed( String destClientID )
    {
        LinkedList<OutgoingMessage> expectedMessages = null;
        pre
        {
            expectedMessages = messagesToSend.get( destClientID );
            if( expectedMessages == null )
                return false;
            return   (    expectedMessages.size() == 1
                       && expectedMessages.get( 0 ) == notSubscribed
                     )
                   : "'NotSubscribed' message must be sent to " + destClientID
                 ;
        }
    }