Архитектура среды тестирования на основе моделей
Инструменты модульного тестирования
Рассмотрим теперь имеющиеся инструменты тестирования с точки зрения их приближения к желаемому идеалу — инструментарию на основе компонентной технологии.Важным классом инструментов тестирования, в большой степени обладающих свойствами модульности, являются средства модульного тестирования (unit testing) . Наиболее известен из таких инструментов JUnit , написанный на Java и предназначенный для тестирования кода на этом языке, хотя исторически первым был SUnit для программ на Smalltalk.
Для этих инструментов характерны высокая гибкость, возможность подключения совершенно независимых модулей для реализации дополнительных функций и возможность использования в рамках более сложных тестовых систем. Одним из инструментов модульного тестирования, обладающих наиболее богатой функциональностью, является TestNG . Его основные характеристики таковы.
Инструменты модульного тестирования активно используют развиваемые независимо модули для решения разных более специфичных задач. Например, dbUnit — для организации работы с базами данных в модульных тестах используется, httpUnit — для обработки HTTP-запросов. Для более наглядной записи выполняемых в тестах проверок (близкой к формулировкам естественных языков) можно применять библиотеки, предоставляемые инструментами разработки на основе функциональности (behavior driven development), например, JBehave или NSpecify , для организации тестовых заглушек — библиотеки Mockito , EasyMock и т.д.
Инструменты тестирования на основе моделей
Инструменты тестирования на основе моделей с точки зрения их приближения к желательной компонентной архитектуре можно разделить на три группы.К инструментам такого типа относятся практически все многочисленные исследовательские прототипные средства тестирования на основе моделей и ряд более стабильных инструментов, использовавшихся во многих разных проектах. В этой второй категории находятся TorX , TGV , BZ-TT и Gotcha-TCBeans . Все они основаны на моделировании проверяемой системы в виде различных автоматных моделей. На тех же принципах в целом построены и коммерческие инструменты Conformic Qtronic и Smartesting Test Designer (ранее Leirios) .
Такие инструменты начали появляться не так давно, около 4-5 лет назад. Два наиболее известных примера — это ModelJUnit и NModel . Похожий инструмент mbt.tigris.org использует для описания моделей графическую нотацию, поэтому гораздо менее приспособлен для использования в рамках чужих разработок.
Модели для композиции указываются инструменту построения тестов перечислением имен соответствующих классов.
Библиотека CodeContracts предоставляет средства для описания чисто декларативных ограничений на свойства входных параметров и результатов операций. Моделирование состояния не поддерживается. Имеются следующие возможности.
Итоги обзора существующих инструментов
Из приведенного обзора видно, что компонентные средства тестирования на основе моделей в последние годы активно развиваются, однако пока не достигли необходимой функциональности, отставая по ряду возможностей от инструментов модульного тестирования. Необходимо дополнить имеющиеся наработки следующими возможностями.Тестирование на основе моделей и инструменты тестирования
Тестирование на основе моделей (model based testing) представляет собой подход к тестированию, в рамках которого тесты строятся вручную, автоматизированным образом или генерируются полностью автоматически на основе модели поведения тестируемой системы и модели ситуаций, связанных с ее работой.Модель поведения служит основой для тестового оракула — компонента или процедуры, производящей оценку поведения системы во время тестирования.
Чаще всего при тестировании используются модели в виде автоматов разного вида: наиболее простые конечные автоматы, расширенные автоматы, системы помеченных переходов , Statecharts , временные автоматы и т.д. Однако в таком качестве можно использовать другие разновидности моделей: контрактные спецификации в виде пред- и постусловий операций, алгебраические спецификации в виде правил эквивалентности различных цепочек вызовов операций, трассовые или сценарные модели, описывающие возможные последовательности воздействий и реакций системы.
Модель ситуаций используется для решения двух тесно связанных задач: определения критерия адекватности или полноты тестирования и определения числовых метрик полноты тестирования. Заметим, что под полнотой тестирования здесь имеется в виду удовлетворение определенному критерию (скажем, достижение 80% покрытия кода), а совсем не исчерпывающий перебор всех практически возможных ситуаций. Критерий полноты тестирования задает свойства набора тестов, позволяющие считать его достаточно хорошо представляющим разнообразное поведение проверяемой системы для решения некоторого круга задач и больше тестов не создавать. Метрики полноты тестов представляются в терминах процентной доли проверяемых ими классов ситуаций разных видов. Обычно в качестве критерия полноты тестирования устанавливается достижение какого-то значения метрики, но при этом само значение метрики бывает полезно знать дополнительно. Чаще всего полнота тестирования определяется на основе покрытия классов эквивалентных ситуаций или элементарных событий, и в этих случаях говорят о критерии тестового покрытия, метрике тестового покрытия, а достигнутый в ходе тестирования процент покрытых классов ситуаций называют тестовым покрытием.
Построение тестов на основе моделей заключается в том, что создается (извлекается из проектных документов или, реже всего, берется откуда-то в готовом виде) модель поведения тестируемой системы, создается модель ситуаций, отражающая основные приоритеты и риски проекта и, чаще всего, использующая структурные элементы модели поведения, и затем строится (генерируется автоматически, создается вручную, или разрабатывается с существенной помощью инструментов) комплект тестов, проверяющих соответствие между реальным поведением тестируемой системы и ее моделью поведения. При этом тестовый набор создается так, чтобы он удовлетворял критерию полноты тестирования, заданному моделью ситуаций.
Используемые для построения тестов методы можно разделить три типа.
При этом полноту тестирования стараются обеспечить за счет большого количества разнообразных тестов, так как построение каждого отдельного теста требует небольших затрат.
Требования к представлениям моделей
Инструменты, поддерживающие тестирование на основе моделей, должны работать с какими-то представлениями моделей поведения и моделей ситуаций. Для полноценной поддержки этого подхода к тестированию и повышения удобства его использования в практике разработки ПО, эти представления должны обладать следующими свойствами.На практике критерии полноты тестирования используют разнообразные свойства для характеризации ситуаций: структуру входных данных и результатов, внутренние состояния системы, выполняемые инструкции кода и вызываемые внутри функции, шаблоны передачи данных внутри системы, шаблоны взаимодействия различных частей системы при выполнении операции, характеристики ожидаемого поведения, возможные ошибки в системе и пр.
Как видно, требования к представлению моделей делятся на три типа: достаточная для практических целей выразительность, прослеживаемость к требованиям и возможность автоматического решения различных задач построения тестов.
Архитектурный каркас для тестирования на основе моделей
В данном разделе описывает предлагаемый подход к построению компонентной архитектуры инструментов тестирования на основе моделей, удовлетворяющей сформулированным выше требованиям. Однако прежде стоит сделать ряд замечаний, касающихся выбираемых средств решения поставленных задач.При моделировании программных интерфейсов с устоявшимися требованиями достаточно удобно применять контрактные спецификации в виде пред- и постусловий, опирающихся на модельное состояние моделируемых компонентов. Подходы на их основе продемонстрировали достаточную масштабируемость и эффективность в терминах трудозатрат на описание некоторого набора элементов интерфейса . C другой стороны, для моделирования вычислений с плавающей точкой, сложных протоколов и ряда других видов ПО, иногда более удобно использовать операционные модели, являющиеся, по сути, альтернативными реализациями той же функциональности. Наиболее удобными на практике моделями такого вида оказываются расширенные автоматы и системы переходов с возможностью их композиции.
При моделировании некоторых реактивных систем, обрабатывающих большие потоки событий, или служб, предназначенных для регулярной обработки данных из большой базы, полезными оказываются потоковые контракты, описывающие ограничения не на конечный результат обработки, а на обработку одного структурного элемента во входном потоке данных
Такой подход позволит применять для работы с этими моделями и для их интеграции с проверяемыми компонентами все инструменты, средства и техники, предлагаемые базовой компонентной технологией. Соответственно, значительно снижаются затраты на сопровождение и развитие инструментария, поддерживающего такую технологию. Это обеспечивает возможность использования того же инструментария и созданных моделей компонентов при разработке тестов для подсистем и крупных систем. Тем самым создается основа для выполнения требований к компонентным технологиям верификации.
Реализация предложенного подхода
Для реализации предложенной архитектуры в качестве базового языка программирования был выбран язык Java. Он обладает многими языковыми возможностями, необходимыми для описания различных ролей классов и методов, а также связей между компонентами: средствами описания декларативной информации об элементах кода в виде аннотаций и поддержкой получения в динамике информации о структуре компонентов и сигнатурах их операций (интроспекция или рефлексия).В качестве контейнера внедрения зависимостей была выбрана открытая библиотека Spring , поддерживающая достаточно большой набор функций системы такого типа.
Для описания моделей поведения была разработана небольшая библиотека, похожая, с одной стороны, на библиотеки проверки утверждений в средствах модульного тестирования (используются разнообразные методы assert()) и, с другой стороны, на Microsoft CodeContracts (для доступа к результату операции и пре-значениям выражений используются методы result() и oldValue()). В отличие от CodeContracts поддерживается создание контрактных спецификаций, использующих модельное состояние. В разработанной библиотеке отсутствуют имеющиеся в CodeContracts кванторные выражения и поддержка статического анализа ограничений.
Для описания моделей ситуаций также создана небольшая библиотека, обеспечивающая трассировку информации о покрытии указываемых ситуаций.
Тесты оформляются в стиле, аналогичном ModelJUnit и NModel, но с некоторыми расширениями, частично заимствованными из TestNG.
Значения параметров извлекаются из связанного с тестовым методом провайдера данных. Провайдер может быть генератором наборов значений, определенных, например, как элементы некоторой коллекции, а может быть построен динамически из генераторов данных для разных параметров с определенной стратегией их комбинирования (выбирать все комбинации, каждое значение каждого параметра, все возможные пары значений и пр.). Провайдеры данных и способ их комбинирования задаются с помощью аннотаций метода и его отдельных параметров.
Для построения заглушек используется свободная библиотека Mockito . Она имеет достаточно богатые возможности для определения управляющих и наблюдающих заглушек и использует интуитивно понятный синтаксис при их описании. Этот пример показывает, что при наличии Java-библиотеки с необходимой функциональностью, она без особых усилий может быть использована в рамках предлагаемой архитектуры.
Виды компонентов и их интеграция
Основой инструментария тестирования на основе моделей предлагается сделать контейнер внедрения зависимостей (dependency injection container), позволяющий задавать список компонентов, входящих в систему, инициализировать их состояние и определять связи между ними внешним образом, без вмешательства в код этих компонентов.Верификационная система строится из компонентов различных типов.
На них накладывается только одно ограничение: возможность их внешней инициализации с помощью контейнера внедрения зависимостей. В большинстве случаев это ограничение выполняется, иначе обычно достаточно просто написать компонент-обертку, удовлетворяющий ему и предоставляющий доступ к проверяемым операциям исходного компонента.
Проверяемые компоненты не имеют зависимостей от тестовой системы, за исключением заглушек, подменяющих необходимые им для работы компоненты.
Они оформляются на базовом языке программирования как классы с несколькими методами, выполняющими определенные роли. Например, если используется чисто декларативная спецификация, в ней должны быть определены пред- и постусловия, причем любой метод, возвращающий булевское значение, может играть эти роли. Для спецификации, использующей модельное состояние компонента, необходимо определить синхронизатор состояния, вызываемый, чтобы поддерживать в соответствии состояние модели и реальное состояние проверяемого компонента. Исполнимые спецификации должны определять предусловия и модельные операции.
Модели поведения зависят от проверяемых компонентов или, в случае существенных различий в интерфейсах между моделью и моделируемым компонентом — от адаптеров, устраняющих такие различия.
При описании многокомпонентных систем иногда, помимо моделей отдельных компонентов, необходимо явно вводить модель их взаимодействия, позволяющую оценить корректность сложных конгломератов воздействий и реакций, в которых задействовано несколько компонентов, каждый из которых осведомлен лишь о событиях, относящихся к его интерфейсу.
Например, моделью взаимодействия является так называемая семантика чередования для асинхронных взаимодействий параллельно работающих компонентов, в рамках которой корректен любой набор событий, который можно линейно упорядочить так, чтобы каждое отдельное событие в таком порядке стало корректным относительно моделей компонентов, создающих или обрабатывающих его .
Модели взаимодействия оформляются в виде шаблонных библиотечных модулей, привязываемых в конфигурационном файле к соответствующим им группам компонентов. Для каждого конкретного взаимодействия порождается экземпляр такого шаблона, зависящий от моделей поведения вовлеченных в него компонентов.
Модели ситуаций оформляются на базовом языке программирования в виде методов, фиксирующих наступление определенных ситуаций после проверки описывающих их ограничений. Модели ситуаций для некоторой операции могут включать как пре-ситуации, определяемые входными аргументами операции и состояниями вовлеченных компонентов до ее вызова, так и пост-ситуации, соответствующие определенным свойствам результатов и состояний компонентов после работы операции. Модели пост-ситуаций могут иметь модельное состояние и методы-синхронизаторы, так же, как и контрактные спецификации.
Модели ситуаций зависят от проверяемых компонентов, моделей поведения или теста, сообразно тому, в каких терминах они описывают ситуации.
Модели ситуаций могут быть сгенерированы автоматически из моделей поведения, интерфейсов и кода проверяемых компонентов, поскольку критерии полноты тестирования на основе структуры кода или функциональности часто используются при построении тестов. Такие компоненты, генерируемые из других, далее будем называть вторичными.
Тесты, так же, как и модели ситуаций, могут создаваться разработчиками или генерироваться из моделей поведения и интерфейсов тестируемых компонентов. Каждый тест должен определять последовательность обращений к операциям тестируемого компонента (быть может, состоящую из единственного обращения) и значения параметров этих обращений, тестовые данные.
В этом случае автоматная модель теста должна определять методы, играющие роль действий, охранных условий, а также возвращающие текущее состояние автомата. Модель теста может зависеть от проверяемого компонента или от его модели поведения.
Адаптеры устраняют возможные расхождения между интерфейсами моделей и моделируемых ими компонентов.
Адаптеры зависят от проверяемых компонентов. В тех случаях, когда они отвечают за синхронизацию модельного состояния, имеется зависимость и от соответствующей модели поведения.
Можно отметить, что во многих случаях небольшие расхождения между модельным и проверяемым интерфейсами не требует написания адаптера, поскольку могут быть устранены указанием библиотечной процедуры преобразования. Это относится к случаям, в которых различия сводятся к отсутствию ряда параметров, перестановке параметров местами или простым преобразованиям типов, например, чисел в строки и обратно. Во всех этих случаях адаптер строится не вручную, а автоматически, лишь по указанию соответствующего преобразования в конфигурационном файле тестовой системы.
Заглушки подменяют во время теста компоненты, от которых зависят проверяемые. Они бывают двух видов.
В принципе, одна заглушка может играть обе роли одновременно, но на практике такая потребность возникает крайне редко (это признак очень сложной организации теста, которую, возможно, имеет смысл пересмотреть).
Если заглушки используются, проверяемый компонент зависит от них. Тест или модель поведения зависят от наблюдающей заглушки, которую они используют. И наоборот, управляющая заглушка сама зависит от теста, поскольку именно тест должен определять результаты очередного вызова ее операций.
Рисунок 1 демонстрирует одну из возможных конфигураций тестовой системы на основе предлагаемой архитектуры. Связи между компонентами, изображенные на рисунке, представляют собой зависимости, характерные для компонентов такого типа (хотя не все возможные зависимости изображены). Связи генератора трассы и конфигуратора не показаны, поскольку все или почти все компоненты связаны с ними.
Рисунок 1. Схема построения тестовой системы.
Конкретный набор компонентов и связи между ними описываются в конфигурационном файле в XML-формате, поступающем в начале работы на вход контейнеру внедрения зависимостей, который инициализирует все компоненты и связывает их нужным образом. Такой способ задания связей позволяет строить различные конфигурации тестовой системы, не меняя кода ее компонентов, и даже не имея к нему доступа. Вместе с тем, возможно определение жестких связей в самом коде, а также более гибкое связывание с помощью аннотаций и создание специальных компонентов-конфигураторов, которые содержат явную инициализацию компонентов и связей между ними на базовом языке программирования.
Пример построения теста
Далее описывается пример использования предложенных решений при построении тестов для простой реализации функциональности банковского счета. Интерфейс тестируемого >public interface Account { int getBalance(); int getMaxCredit();
Validator getValidator(); void setValidator(Validator p);
AuditLog getLog(); void setLog(AuditLog log);
int transfer(int sum); }
Методы getBalance() и getMaxCredit() служат для получения текущих значений баланса и максимально возможного кредита. Баланс не может быть отрицательным и превосходящим максимально возможный кредит по абсолютной величине.
Метод int transfer() осуществляет перевод денег со счета или на счет, в зависимости от знака своего аргумента. Если аргумент положителен, соответствующая сумма добавляется на счет, увеличивая его текущий баланс. Если отрицателен, эта сумма списывается со счета, если при этом баланс не выходи за рамки максимального кредита. Результат этого метода — переведенная сумма или 0, если перевод не был сделан.
Данный счет позволяет использовать специализированный валидатор транзакций, Validator, который опрашивается при любом переводе с помощью предоставляемого им метода boolean validateTransfer(Account a, int sum) и может разрешить или заблокировать перевод.
Еще одна функция счета — запись данных о попытках перевода денег в трассу для последующего аудита. При этом вызываются методы интерфейса AuditLog: logKind(String s), logOldBalance(int b), logSum(int sum), logNewBalance(int b), записывающие, соответственно, итог транзакции (SUCCESS в случае успешного перевода, BANNED в случае его блокировки валидатором, IMPROPER в случае попытки снятия слишком большой суммы), предшествующее значение баланса, переводимую сумму и новое значение баланса.
Модель поведения для счета описана в виде двух независимых компонентов: модели основной функциональности и модели работы с трассировкой переводов. Это позволяет изменять и проверять эти две группы ограничений независимо. Описание основной функциональности выглядит так. public class AccountContract { int balance; int maxCredit;
Account checkedObject;
public void setCheckedObject(Account checkedObject) { this.checkedObject = checkedObject; this.balance = checkedObject.getBalance(); this.maxCredit = checkedObject.getMaxCredit(); }
public boolean possibleTransfer(int sum) { if (balance + sum > maxCredit) return true; else return false; }
public boolean transferPostcondition(int sum) { boolean permission = checkedObject.getValidator().validateTransfer(checkedObject, sum);
if (Contract.oldBooleanValue(possibleTransfer(sum)) && permission) return Contract.assertEqualsInt(Contract.intResult(), sum , "Result should be equal to the argument") && Contract.assertEqualsInt(balance, Contract.oldIntValue(balance)+sum , "Balance should be increased on the argument") && Contract.assertEqualsInt(maxCredit, Contract.oldIntValue(maxCredit) , "Max credit should not change"); else return Contract.assertEqualsInt(Contract.intResult(), 0 , "Result should be 0") && Contract.assertEqualsInt(balance, Contract.oldIntValue(balance) , "Balance should not change") && Contract.assertEqualsInt(maxCredit, Contract.oldIntValue(maxCredit)
, "Max credit should not change"); }
public void transferUpdate(int sum) { if( possibleTransfer(sum) && checkedObject.getValidator().validateTransfer(checkedObject, sum))
balance += sum; } }
Здесь показаны постусловие метода transfer() и соответствующий синхронизатор модельного состояния.
Описание требований к работе с трассой для аудита дано ниже. Оно использует свободно распространяемую библиотеку для организации заглушек Mockito, вставляя заглушку для наблюдения за сделанными вызовами между счетом и связанным с ним трассировщиком. В ходе работы заглушка проверяет, что методы трассировщика вызывались в нужном порядке и с нужными аргументами. Поскольку построенная заглушка имеет модельное состояние, в ней также определен метод-синхронизатор этого состояния.
Заглушка должна инициализироваться после каждого вызова transfer(), для этого в ней определен метод initSpy(). public class AccountLogSpy { int balance; int maxCredit;
Account checkedObject; AuditLog logSpy;
public void setCheckedObject(Account checkedObject) { this.checkedObject = checkedObject; this.balance = checkedObject.getBalance(); this.maxCredit = checkedObject.getMaxCredit(); logSpy = Mockito.spy(checkedObject.getLog()); checkedObject.setLog(logSpy); }
int oldBalance; boolean wasPossible;
public boolean possibleTransfer(int sum) { if (balance + sum > maxCredit) return true; else return false; }
public void initSpy(int sum) {
Mockito.reset(logSpy); oldBalance = balance; }
public void transferLogSpy(int sum) { boolean permission = checkedObject.getValidator().validateTransfer(checkedObject, sum);
if (wasPossible && permission) {
Mockito.verify(logSpy).logKind("SUCCESS"); Mockito.verify(logSpy).logNewBalance(balance); } else if (!permission)
Mockito.verify(logSpy).logKind("BANNED"); else
Mockito.verify(logSpy).logKind("IMPROPER");
Mockito.verify(logSpy).logOldBalance(oldBalance); Mockito.verify(logSpy).logSum(sum); }
public void transferUpdate(int sum) { if( possibleTransfer(sum) && checkedObject.getValidator().validateTransfer(checkedObject, sum)) {
wasPossible = true; balance += sum; } else
wasPossible = false; } }
Описание модели ситуаций представлено ниже. В ней ситуации классифицируются по четырем характеристикам: корректность перевода, прохождение валидации, знак предшествовавшего значения баланса и знак переводимой суммы. Поскольку определение ситуации зависит от модельного состояния счета и нуждается в синхронизаторе состояния, эта модель наследует модели функциональности, используя повторно определенные в ней элементы кода. public class AccountCoverage extends AccountContract { public void transferCoverage(int sum) { boolean permission = checkedObject.getValidator().validateTransfer(checkedObject, sum);
if (possibleTransfer(sum)) Coverage.addDescriptor("Possible transfer"); else Coverage.addDescriptor("Too big sum");
if (permission) Coverage.addDescriptor("Permitted"); else Coverage.addDescriptor("Not permitted");
if(balance == 0) Coverage.addDescriptor("Zero balance"); else if(balance > 0) Coverage.addDescriptor("Positive balance"); else Coverage.addDescriptor("Negative balance");
if(sum == 0) Coverage.addDescriptor("Zero sum"); else if(sum > 0) Coverage.addDescriptor("Positive sum"); else Coverage.addDescriptor("Negative sum"); } }
Модель теста для счета выглядит следующим образом. @Test public class AccountTest {
Account account; boolean permission = true;
@Mock Validator validatorStub;
public AccountTest() {
MockitoAnnotations.initMocks(this); Mockito.when(validatorStub.validateTransfer(Mockito.
, Mockito.anyInt())).thenReturn(true); }
public void setAccount(Account account) { this.account = account; account.setValidator(validatorStub); }
public Validator getPermitterStub() { return validatorStub; }
@State public int getBalance() { return account.getBalance(); }
@State public boolean getPermission() { return permission; }
@Test @DataProvider(name = "sumArray")
@Guard(name = "bound") public void testDeposit(int x) {
account.transfer(x); }
@Test @DataProvider(name = "sumIterator") public void testWithdraw(int x) {
account.transfer(-x); }
@Test @Guard(name = "bound") public void testIncrement() {
account.transfer(1); }
@Test public void switchPermission() {
permission = !permission; Mockito.when(validatorStub.validateTransfer(Mockito.
, Mockito.anyInt())).thenReturn(permission); }
public boolean bound() { return getBalance() < 5 !permission; }
public int[] sumArray = new int[]{1, 2};
public Iterator
Состояние теста состоит из двух элементов: текущего значения баланса и значения поля permission, определяющего результаты работы управляющей заглушки валидатора. Тестирование снятия денег и помещения их на счет разнесено по разным тестовым методам, хотя при этом вызывается один и тот же метод тестируемого объекта. Всего имеется четыре тестовых метода, соответствующих действиям в описываемом автомате.
Наконец, конфигурационный файл для среды Spring, определяющий связи между всеми перечисленными компонентами, выглядит так.
В этой конфигурации указано, как инициализировать объекты всех перечисленных типов, и, кроме того, определена привязка постусловий и синхронизаторов всех моделей к методу transfer() с помощью поддерживаемой Spring техники привязки аспектов.
Приведенный пример демонстрирует неинвазивность использованного метода построения тестовой системы из заданных компонентов — все эти компоненты ничего не знают друг о друге, кроме типов объектов, от которых они непосредственно зависят. В данной конфигурации модель основной функциональности и модель ситуаций представлены разными объектами, однако, поскольку вторая наследует первой, можно было бы реализовать их при помощи одного и того же компонента, играющего две разные роли.
Программирование: Языки - Технологии - Разработка
- Программирование
- Технологии программирования
- Разработка программ
- Работа с данными
- Методы программирования
- IDE интерфейс
- Графический интерфейс
- Программирование интерфейсов
- Отладка программ
- Тестирование программ
- Программирование на Delphi
- Программирование в ActionScript
- Assembler
- Basic
- Pascal
- Perl
- VBA
- VRML
- XML
- Ada
- Lisp
- Python
- UML
- Форт
- Языки программирования