Тестирование софта - статьи

       

Описание системы


Рассмотрим пример системы, построенной из компонентов, которые взаимодействуют посредством удаленного вызова методов. Система написана на основе Enterprise Java Beans 3.0 []. Система содержит один сеансовый компонент с состоянием (stateful session bean) CompanyManagerBean, реализующий удаленный интерфейс CompanyManager, и один класс сущность (entity) Company (Рис. 1).

Рис. 1.Система управления компаниями

Компонент CompanyManagerBean предоставляет интерфейс работы с иерархиями компаний. У каждой компании имеются уникальный идентификатор, имя, могут иметься активный/неактивный статус и родительская компания. Через компонент CompanyManagerBean можно редактировать компании. Клиент запрашивает экземпляр компонента, затем устанавливает компанию, которую собирается редактировать (editCompany), блокируя доступ к данной компании другим экземплярам, редактирует компанию, изменяя статус компании (setActive, setInactive) и родительскую компанию (setParent, removeParent), затем освобождает компанию (freeCompany), делая ее доступной для редактирования другим экземплярам.

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

  1. Компания с активным статусом не может иметь родительскую компанию с неактивным статусом;
  2. Иерархия компаний более четырех уровней (глубины) недопустима.

Таким образом, данные ограничения запрещают иерархии, показанные на Рис. 2: (а) - активная компания C2 имеет неактивного родителя C1, (б) - иерархия компаний имеет более четырех уровней.

Рис. 2. Примеры запрещенных иерархий

Реализация методов компонента CompanyManagerBean состоит в предварительной проверке ограничений и, в случае успеха, изменении состояния компании и сохранении результатов изменения в базе данных. Для примера рассмотрим реализации методов setInactive и setParent. Метод setInactive устанавливает статус компании неактивным (Рис. 3). Предварительно метод проверяет, что все дочерние компании имеют неактивный статус. Проверка производится во вспомогательном методе isChildrenInactive, в котором происходит поисковый запрос к базе данных с выбором всех дочерних компаний, т.е.
компаний, у которых родительская компания совпадает с данной. Далее, если все дочерние компании неактивны, то возвращается true, статус компании меняется на неактивный, и изменения сохраняются в базе данных (enityManager.merge), иначе возвращается false, и статус компании не изменяется. Обращения к базе данных производятся через специальный компонент EntityManager, который предоставляет методы добавления (persist), изменения (merge), поиска (find) записей в базе данных. Отметим, что для корректности результатов данной работы требуется, чтобы обращения компонентов к общим данным, хранящимся в базе данных, являлись атомарными, т.е. одновременно может выполняться запрос не более чем от одного компонента. В данном примере это требование обеспечивается механизмом транзакций EJB таким образом, что обращения к EntityManager включаются в контекст транзакций. public boolean setInactive() { if (isChildrenInactive()) { company.setActive(false); entityManager.merge(company); return true; } else { return false; Рис. 3. Реализация метода setInactive public boolean setParent(int id) { Company newParent = entityManager.find(Company.class, id); if (newParent == null) throw new NoResultException(); if (newParent.getActive() !company.getActive()) { company.setParent(newParent); entityManager.merge(company); return true; } else { return false; } } Рис. 4. Реализация метода setParent Метод setParent устанавливает родительскую компанию (Рис. 4). Требуется, чтобы существовала компания с идентификатором, указанным в качестве аргумента вызова этого метода. Метод проверяет, что для активной компании нельзя установить неактивного родителя. Т.е. если родитель активный или компания неактивная, то устанавливается новый родитель, и изменения сохраняются в базе данных; иначе изменения не происходят. Рассмотрим один из возможных тестов для компонента CompanyManagerBean. В тесте предполагается, что в начальном состоянии имеются две активные компании без родителей.


Имена компаний C1, C2, а идентификаторы 1, 2 соответственно. Тест состоит из двух потоков, выполняющихся одновременно. Оба потока выполняют метод testCase, показанный на Рис. 5, с разными аргументамии. Первый выполняет метод с аргументами 0, 1, 2 а второй - с аргументами 1, 2. Таким образом, первый поток захватывает компанию C1 на редактирование и, в случае успеха, вызывает метод setParent с аргументов - идентификатором компании C2 (будем писать C1.setParent(C2)), затем освобождает компанию. Второй захватывает компанию C2 и, в случае успеха, вызывает метод setInactive (будем писать C2.setInactive()), затем освобождает компанию. Для создания экземпляра компонента CompanyManagerBean используется вспомогательный метод getCompanyManager. public static void testCase(int num, int id, int parent) throws Exception { CompanyManager manager = getCompanyManager(); manager.editCompany(id); try { if (num==0) { manager.setParent(parent); } else { manager.setInactive(); } } finally { manager.freeCompany(); } } Рис. 5. Тестовый вариант Результаты выполнения данного теста зависят от того, в какой последовательности выполнялись удаленные вызовы методов (рис. 6). Если сначала полностью выполнится первый поток, а затем второй, то в результате для компании C1 будет установлен родитель C2, а статус компании C2 по-прежнему останется активным, так как метод isChildrenInactive обнаружит у C1 активного ребенка C2. Если сначала выполнится второй поток, а затем первый, то в результате статус компании C2 станет неактивным, но родитель для C1 установлен не будет, так как в момент проверки статус родительской компании C2 будет неактивным. Однако возможен еще и третий вариант, когда проверки в методах setParent и setInactive выполняются до того, как происходят изменения в базе данных, т.е. выполняются методы entityManager.merge. Например, пусть вызовы выполняются в следующей последовательности: t1.editCompany; t1.setParent; s1.find; t2.editCompany; t2.setInactive; s2.find; s1.merge; s2.merge; t1.freeCompany; t2.freeCompany где префиксы t1, t2 означают, что вызовы происходили из первого и второго потоков соответственно, а s1, s2 - соответствующие экземпляры CompanyManagerBean.Результатом такого выполнения будет установка для C1 родительской компании C2, а для C2 - установка неактивного статуса, что нарушает первое ограничение.

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

Содержание раздела