Testy złożonych systemów i aplikacji wymagają zdefiniowania oraz weryfikacji ogromnej liczby przypadków testowych pokrywających wszystkie wymagania stawiane przez klienta. Aby umożliwić sprawne przeprowadzenie testów konieczne jest umiejętne zarządzanie zespołem testerów, a także dobra komunikacja w jego obrębie. Celem projektu jest stworzenie aplikacji internetowej, stanowiącej odpowiedź na tą potrzebę biznesową. Aplikacja ta ułatwi pracę zespołów zajmujących się testowaniem oprogramowania poprzez umożliwienie im wygodnego zarządzania wymaganiami i przypadkami testowymi oraz usprawnienie samego procesu wykonywania testów. Istotnym elementem aplikacji będzie również funkcjonalność zarządzania zespołem testerów, przydzielania im ról oraz zarządzania poziomem dostępu do poszczególnych projektów. Projekt realizowany będzie przy współpracy z firmą ATSI S.A., zajmującą się tworzeniem oprogramowania. Wymiana doświadczeń z testerami pracującymi na co dzień z tego typu narzędziami pozwoli określić czego brakuje aktualnie dostępnym rozwiązaniom oraz zidentyfikować możliwości wprowadzenia nowych funkcjonalności, które pozytywnie wpłyną na użyteczność oprogramowania. W efekcie powstanie rozwiązanie stanowiące atrakcyjną alternatywę dla już istniejących.
Opracowanie projektowanej aplikacji wymaga dokładnej analizy środowiska, w którym przyjdzie jej działać. W ogólnym zarysie na środowisko to składa się wewnętrzna sieć firmy oraz wszystkie systemy i narzędzia wykorzystywane przez testerów, pracujące w tej sieci. Głównym przedmiotem naszego zainteresowania jest aktualnie wykorzystywane w firmie narzędzie wspomagające pracę testerów w zakresie definiowania wymagań, opisywania przypadków testowych i wykonywania testów. Narzędzie to nosi nazwę Testlink (http://www.teamst.org/) i jest aplikacją webową na licencji Open Source o bardzo rozbudowanej funkcjonalności. Do głównych zalet Testlinka należy zaliczyć dostępność API XML/RPC umożliwiającego pobieranie wyników testów oraz współpracę z wieloma systemami śledzenia błędów. Mimo powyższych zalet aplikacja Testlink posiada też kilka mankamentów, które sprawiają, że nie spełnia wszystkich wymagań klienta. Należy tutaj wymienić mało przyjazny dla użytkownika interfejs, narzucenie modelu procesu testowania niezgodnego z modelem stosowanym w firmie, a także brak integracji z narzędziami do automatyzacji testów.
Ważnymi elementami składowymi środowiska docelowego dla projektowanego oprogramowania są narzędzia takie jak Jira i Hudson. Ich obecność wymusza uwzględnienie na etapie projektowym rozwiązań, które pozwolą na zintegrowanie z nimi tworzonej aplikacji. Konieczne jest umożliwienie testerowi raportowania błędów do Jiry z poziomu aplikacji w przypadku wykrycia błędu podczas testowania, a także zapewnienie funkcjonalności łączenia przypadków testowych i wymagań z odpowiednimi testami automatycznymi wykonywanymi przez Hudsona.
Główne wymagania funkcjonalne systemu:
Wymagania związane z interfejsem użytkownika:
Konto użytkownika:
Konta użytkowników z punktu widzenia administratora:
Projekty i zespoły:
Wymagania i ich zestawy:
Przypadki testowe i ich zestawy:
Raporty, panel zbiorczy, wyszukiwanie:
Testowanie:
Podstawowe funkcje realizowane przez system na bazie danych będą skupiały się wokół przechowywania i udostępniania informacji na temat:
W systemie zidentyfikowano następujące encje:
Atrybuty tych encji oraz relacje pomiędzy encjami zaprezentowano na poniższym diagramie ERD.
Należy w tym miejscu dodać, że dla każdego klucza głównego w bazie danych automatycznie tworzony jest indeks typu B-tree, co jest domyślnym zachowaniem systemu PostgreSQL.
Tabela REQUIREMENT_SETS:
Tabela PROJECTS:
Tabela GROUPS:
Tabela STEPS:
Tabela REQUIREMENTS:
Tabela TESTCASES:
Tabela USERS:
Tabela TESTCASE_SETS:
Wszystkie atrybuty encji w przedstawionym modelu są atomiczne, a więc baza danych spełnia warunek pierwszej postaci normalnej.
Aby spełniona była druga postać normalna wszystkie atrybuty niekluczowe encji muszą zależeć od całego klucza głównego tej encji. W przedstawionym schemacie wszystkie tabele z wyjątkiem PROJECT_USERS i TESTCASE_REQUIREMENTS posiadają klucz główny prosty, a więc warunek ten jest spełniony. Z kolei tabele PROJECT_USERS oraz TESTCASE_REQUIREMENTS są typowymi tabelami asocjacyjnymi i nie posiadają atrybutów niekluczowych.
Baza danych spełnia trzecią postać normalną. W żadnej z encji opracowanego modelu nie występują relacje tranzytywne, wszystkie atrybuty zależą bezpośrednio od klucza głównego.
W projekcie zostanie wykorzystane rozwiązanie ORM Hibernate, które przejmuje od nas odpowiedzialność za operowanie na bazie danych. Poniżej przedstawiona została przykładowa implementacja klasy-encji, reprezentującej przypadek testowy:
package pl.edu.agh.testo.data; @Entity @SequenceGenerator(initialValue = 1, name = "idgen", sequenceName = "testcase_seq") public class TestCase implements Serializable { @Id @GeneratedValue(strategy = GenerationType.AUTO, generator = "idgen") private Long testCaseId; private String name; private boolean blocked; private Date created; private Date updated; private TestCasePriority priority; private String prerequisites; @ManyToOne private AppUser author; @ManyToOne private TestCaseSet testCaseSet; @ManyToOne private AppUser blockedBy; @ManyToMany @JoinTable( name="testcase_requirement", joinColumns=@JoinColumn(name="testcase_id"), inverseJoinColumns=@JoinColumn(name="requirement_id") ) private Set<Requirement> requirements; @OneToMany(mappedBy = "testCase", cascade = CascadeType.ALL) @Cascade(org.hibernate.annotations.CascadeType.DELETE_ORPHAN) @JoinColumn(name = "testCaseId") private Set<Step> steps; public TestCase() { } public Long getTestCaseId() { return testCaseId; } //getters and setters... }
Reprezentatywne przykłady zapytań SQL generowanych przez Hibernate:
a) pobieranie listy projektów dla panelu zarządzania projektami:
select this_.projectId as projectId0_0_, this_.name as name0_0_ from Project this_ order by this_.projectId asc limit ?
b) aktualizacja nazwy projektu
update Project set name=? where projectId=?
c) tworzenie nowego projektu
insert into Project (name, projectId) values (?, ?)
d) pobranie listy wymagań dla panelu zarządzania wymaganiami
select this_.requirementId as requirem1_4_5_, this_.assignee_appUserId as assignee8_4_5_, this_.author_appUserId as author9_4_5_, this_.content as content4_5_, this_.created as created4_5_, this_.name as name4_5_, this_.numberOfCases as numberOf5_4_5_, this_.requirementSetId as require10_4_5_, this_.requirementStatus as requirem6_4_5_, this_.updated as updated4_5_, appuser3_.appUserId as appUserId6_0_, appuser3_.active as active6_0_, appuser3_.email as email6_0_, appuser3_.group_userGroupId as group8_6_0_, appuser3_.login as login6_0_, appuser3_.name as name6_0_, appuser3_.password as password6_0_, appuser3_.role as role6_0_, usergroup4_.userGroupId as userGrou1_5_1_, usergroup4_.name as name5_1_, appuser5_.appUserId as appUserId6_2_, appuser5_.active as active6_2_, appuser5_.email as email6_2_, appuser5_.group_userGroupId as group8_6_2_, appuser5_.login as login6_2_, appuser5_.name as name6_2_, appuser5_.password as password6_2_, appuser5_.role as role6_2_, rs1_.requirementSetId as requirem1_2_3_, rs1_.name as name2_3_, rs1_.projectId as projectId2_3_, project7_.projectId as projectId0_4_, project7_.name as name0_4_ from Requirement this_ left outer join AppUser appuser3_ on this_.assignee_appUserId=appuser3_.appUserId left outer join UserGroup usergroup4_ on appuser3_.group_userGroupId=usergroup4_.userGroupId left outer join AppUser appuser5_ on this_.author_appUserId=appuser5_.appUserId inner join RequirementSet rs1_ on this_.requirementSetId=rs1_.requirementSetId left outer join Project project7_ on rs1_.projectId=project7_.projectId where rs1_.projectId=? order by this_.requirementId asc limit ?
e) modyfikacja wymagania
update Requirement set assignee_appUserId=?, author_appUserId=?, content=?, created=?, name=?, numberOfCases=?, requirementSetId=?, requirementStatus=?, updated=? where requirementId=?
f) usunięcie wymagania
delete from testcase_requirement where requirementId=? delete from Requirement where requirementId=?
Jak widać zapytania generowane przez Hibernate nie zawsze są optymalne. Przykładowo- pobranie obiektu klasy Requirement pociąga za sobą pobranie całej struktury obiektów powiązanych, a co za tym idzie wykonanie pięciu złączeń, pomimo, że nie potrzebujemy wszystkich zawartych w nich danych. Jest to bolączka wszystkich rozwiązań ORM. Pewną rekompensatą jest zastosowanie cache'u. Inną metodą łagodzenia problemu są różne fetching strategies, które definiuje Hibernate (np. domyślną strategią dla kolekcji jest lazy fetching dzięki czemu kolekcje ładowane są dopiero wówczas kiedy są potrzebne).
Wykorzystanie w projekcie biblioteki Hibernate sprawiło, że implementacja bazy danych sprowadziła się do skonfigurowania samego Hibernate'a oraz utworzenia modelu danych opisanego za pomocą adnotacji zdefiniowanych w standardzie JPA. Zastosowanie rozwiązania ORM pozwoliło na znaczne przyspieszenie prac i uwolniło nas od konieczności utrzymywania schematu bazy zgodnego z aktualnym modelem danych aplikacji.
Poniższy listing przedstawia kod klasy HibernateUtil odpowiedzialnej za konfigurację połączenia z bazą oraz zarządzanie instancją klasy SessionFactory:
public class HibernateUtil { private static final SessionFactory sessionFactory; static { try { AnnotationConfiguration cnf = new AnnotationConfiguration(); cnf.setProperty(Environment.DRIVER, "org.postgresql.Driver"); cnf.setProperty(Environment.URL, "jdbc:postgresql://localhost:5432/tessto"); cnf.setProperty(Environment.USER, "user"); cnf.setProperty(Environment.PASS, "password"); cnf.setProperty(Environment.DIALECT, PostgreSQLDialect.class.getName()); cnf.setProperty(Environment.SHOW_SQL, "true"); cnf.setProperty(Environment.HBM2DDL_AUTO, "create-drop"); cnf.setProperty(Environment.CURRENT_SESSION_CONTEXT_CLASS, "thread"); cnf.addAnnotatedClass(Project.class); cnf.addAnnotatedClass(TestCaseSet.class); cnf.addAnnotatedClass(RequirementSet.class); cnf.addAnnotatedClass(TestCase.class); cnf.addAnnotatedClass(Requirement.class); cnf.addAnnotatedClass(UserGroup.class); cnf.addAnnotatedClass(AppUser.class); cnf.addAnnotatedClass(Step.class); sessionFactory = cnf.buildSessionFactory(); } catch (Throwable ex) { System.err.println("Initial SessionFactory creation failed." + ex); throw new ExceptionInInitializerError(ex); } } public static SessionFactory getSessionFactory() { return sessionFactory; } }
Opcja cnf.setProperty(Environment.HBM2DDL_AUTO, „create-drop”)
powoduje automatyczne wygenerowanie poleceń SQL budujących strukturę bazy i wykonanie ich na bazie.
POJO reprezentujące wymaganie wraz z adnotacjami:
@Entity @SequenceGenerator(initialValue = 1, name = "idgen", sequenceName = "requirement_seq") public class Requirement implements Serializable { @Id @GeneratedValue(strategy = GenerationType.AUTO, generator = "idgen") private Long requirementId; private String name; private String content; private RequirementStatus requirementStatus; private int numberOfCases; private Date created; private Date updated; @ManyToOne private AppUser author; @ManyToOne private AppUser assignee; @ManyToOne @JoinColumn(name = "requirementSetId") private RequirementSet requirementSet; @ManyToMany @JoinTable( name="testcase_requirement", joinColumns=@JoinColumn(name="requirementId"), inverseJoinColumns=@JoinColumn(name="testCaseId") ) private Set<TestCase> testCases; // getters and setters... }
Jak widać Hibernate oferuje szereg ułatwień jak np. automatyczną obsługę relacji many-to-many. Wykorzystując adnotację @ManyToMany
nie musimy sami tworzyć tabel asocjacyjnych- robi to za nas Hibernate.
Panel ten odpowiada stanowi 'Zarządzanie projektami' na diagramie STD i przypadkowi użycia 'Projekty i zespoły: Zarządzanie projektami'.
Panel ten odpowiada stanowi 'Edycja projektu' na diagramie STD i przypadkowi użycia 'Projekty i zespoły: Zarządzanie projektami'.
Panel ten odpowiada stanowi 'Budowanie testów - listy wymagań i przypadków testowych (Test Workbench)' na diagramie STD i przypadkom użycia 'Zarządzanie zestawami przypadków testowych', 'Tworzenie/edycja przypadków testowych', 'Usuwanie przypadków testowych' oraz 'Przeglądanie przypadków testowych' z grupy 'Przypadki testowe i ich zestawy'.
Panel ten odpowiada stanowi 'Budowanie testów - listy wymagań i przypadków testowych (Test Workbench - podgląd przypadku)' na diagramie STD i przypadkom użycia 'Zarządzanie zestawami przypadków testowych' oraz 'Przeglądanie przypadków testowych' z grupy 'Przypadki testowe i ich zestawy'.
Panel ten odpowiada stanowi 'Formularz tworzenia/edycji przypadków testowych' na diagramie STD i i przypadkom użycia 'Zarządzanie zestawami przypadków testowych', 'Tworzenie/edycja przypadków testowych' oraz 'Przypisywanie wymagań do przypadków testowych' z grupy 'Przypadki testowe i ich zestawy'.
Panel ten odpowiada stanowi 'Budowanie testów - listy wymagań i przypadków testowych (Test Workbench)' na diagramie STD i przypadkom użycia 'Zarządzanie zestawami wymagań', 'Usuwanie wymagań' oraz 'Przeglądanie i filtrowanie wymagań' z grupy 'Wymagania i ich zestawy'.
Panel ten odpowiada stanowi 'Budowanie testów - listy wymagań i przypadków testowych (Test Workbench - podgląd wymagania)' na diagramie STD i przypadkom użycia 'Zarządzanie zestawami wymagań' oraz 'Przeglądanie i filtrowanie wymagań' z grupy 'Wymagania i ich zestawy'.
Panel ten odpowiada stanowi 'Formularz tworzenia/edycji wymagań' na diagramie STD i przypadkom użycia 'Zarządzanie zestawami wymagań' oraz 'Tworzenie/edycja wymagań' z grupy 'Wymagania i ich zestawy'.
Aplikacja została stworzona z wykorzystaniem frameworka Vaadin bazującego na technologii GWT. Paczka instalacyjna ma więc postać archiwum WAR obsługiwanego przez dowolny kontener serwletów (Tomcat, Jetty, itp.). Do zainstalowania aplikacji wymagane są następujące elementy:
W razie konieczności skalowania aplikacji można wykorzystać przykładowo serwer HTTP Apache z modułem mod_jk, działający w charakterze proxy/load balancera dla kilku instancji kontenerów.
Wdrożona instancja aplikacji dostępna jest pod adresem http://lukasz.sanek.org.pl/tessto
Dokumentacja powinna zawierać:
W chwili obecnej aplikacja pozwala na wykonywanie podstawowych czynności związanych z zarządzaniem projektami, wymaganiami i przypadkami testowymi. Na dalszych etapach rozwoju należałoby rozważyć rozbudowanie jej o takie funkcjonalności jak:
Jak już zostało wspomniane aplikacja została stworzona z wykorzystaniem frameworka Vaadin, który powstał z myślą o budowaniu aplikacji RIA w Javie. O wyborze technologii zadecydowały takie czynniki jak: obszerna dokumentacja, łatwość wdrożenia się w technologię (dla osób, które korzystały już ze Swinga/AWT lub SWT), aktywna społeczność i możliwość tworzenia aplikacji AJAX bez konieczności poznawania technologii JavaScript. Decyzja okazała się trafna- w krótkim czasie udało się zapoznać z technologią i zrealizować projekt bez większych problemów, w głównej mierze dzięki dużej liczbie dostępnych przykładów i wsparciu ze strony społeczności. Obszerne portfolio gotowych komponentów, a także łatwość wprowadzania własnych modyfikacji przyczyniły się do stworzenia produktu o wysokim poziomie użyteczności.
Należy także wspomnieć o bibliotece Hibernate, której wykorzystanie pozwoliło na znaczne uproszczenie pracy z warstwą modelu i jego persystencją w bazie danych. Dodatkowy narzut czasowy związany z koniecznością poznania kolejnej technologii w ostatecznym rozrachunku okazał się trafioną inwestycją.