tutaj - EduAkcja. Magazyn Edukacji Elektronicznej

Transkrypt

tutaj - EduAkcja. Magazyn Edukacji Elektronicznej
EduAkcja. Magazyn edukacji elektronicznej
nr 2 (10)/2015, str. 39—54
Weryfikacja stylu programowania
w rozwiązaniach zadań programistycznych
Krzysztof Barteczko
Polsko-Japońska Akademia
Technik Komputerowych
[email protected]
Streszczenie: W Polsko-Japońskiej Akademii Technik Komputerowych zrealizowano System Doskonalenia Kwalifikacji Programistycznych, którego głównym celem było stworzenie modelu i narzędzi pogłębionej analizy i oceny
rozwiązań zadań programistycznych w języku Java. Przygotowano m.in. narzędzia automatycznej weryfikacji stylu
programowania oraz spełniania postawionych w zadaniu wymagań. Wyróżniającą cechą proponowanych narzędzi
jest możliwość ustalania przez dydaktyków własnych reguł testowania stylu w różnorodny (dostosowany do ich
wiedzy, umiejętności i upodobań) sposób. Narzędzia te charakteryzują się również prostotą użycia, co ma niebagatelne znaczenia dla ich praktycznego stosowania.
W artykule omówiono sposób implementacji narzędzi weryfikujących oraz metody definiowania testów dla różnych problemów, związanych z jakością programowania. Prezentowane narzędzia i sposoby postępowania mogą
być szczególnie przydatne w zdalnym nauczaniu.
Słowa kluczowe: nauczanie programowania, styl programowania, automatyczna ocena rozwiązań zadań programistycznych, skrypty testujące, Java, Groovy, AST, XPath, PMD
1. Wprowadzenie
Nauczanie programowania sprowadza się nie tylko do przekazywania wiedzy o składni
i semantyce języków programowania czy poprawnej konstrukcji algorytmów w tych językach
(Barteczko, 2012). Ważne jest również nauczenie studentów sposobów tworzenia dobrego jakościowo kodu, czyli właściwego stylu programowania.
Na ten aspekt rzemiosła programistycznego zwracało uwagę wielu renomowanych autorów
(przykładem są klasyczne pozycje Kernighana i Plaugera (Kernighan i Plauger, 1978) oraz
Weinberga (Weinberg, 2011)).
Styl programowania determinuje jakość kodu, a ta z kolei jest ważna dla jego niezawodności, łatwości obsługi i utrzymania (maintainability), adaptowalności i rozszerzalności (Rys. 1).
Decyduje o tym, na ile trudne i kosztowne będzie usuwanie błędów, zauważonych w eksploatacji oprogramowania, czy też jego dostosowanie do zmieniających się wymagań lub ponowne
użycie w innych kontekstach.
Rysunek 1. Znaczenie stylu programowania
Krzysztof Barteczko, Weryfikacja stylu programowania w rozwiązaniach zadań...
Wśród ważnych elementów dobrego stylu programowania wymienić można (por. z: Green,
2000–2014; Hunt i Thomas, 1999; Kernighan i Plauger, 1978; Martin, 2008):
• czytelność,
• zwięzłość,
• prostotę (brak nadmiernej komplikacji kodu),
• bezpieczeństwo,
• dobrą architekturę rozwiązania (design),
• słabe powiązania pomiędzy fragmentami kodu (loose coupling),
• stosowanie właściwych konstrukcji językowych i bibliotek.
W kształtowaniu umiejętności studentów elementy dobrego stylu programowania winny być
prezentowane w postaci przykładowych programów zapisywanych w różny sposób, ze zwróceniem uwagi na zalety i wady każdego z podejść do kodowania. Prezentacje nie będą jednak
wystarczające, jeśli styl rozwiązań zadań programistycznych, zlecanych studentom, nie będzie
analizowany i oceniany. Taka analiza i ocena nie jest łatwa nawet dla pojedynczego rozwiązania.
Przy większej liczbie rozwiązań wymaga zastosowania dodatkowych narzędzi oraz automatyzacji procesu oceny. Jest to również ważne dla wypracowania zobiektywizowanych, utrwalonych
i powtarzalnych procedur weryfikacji stylu i jakości rozwiązań.
Liczne istniejące systemy automatycznej weryfikacji rozwiązań zadań programistycznych
uwzględniają ocenę stylu programowania (por. przeglądowe publikacje: Ala-Mutka, 2005;
Caiza i Del Alamo, 2013; Douce, Livingstone i Orwell, 2005; Striewe i Goedicke, 2014). Do
najbardziej znanych i dojrzałych należą systemy Style++ (por. Ala-Mutka, Uimonen i Jarvinen,
2004) oraz JACK (por. Striewe, Goedicke i Balz, 2008). Rozwiązania te mają jednak pewne wady.
Na przykład, w systemie Style++ zestaw reguł testowania stylu jest zamknięty i dydaktycy nie
mają możliwości definiowania własnych reguł ad hoc. Zaawansowany, oparty na przetwarzaniu
abstrakcyjnych grafów składni (ASG – Abstract Syntax Graph) system JACK pozwala dydaktykom na definiowanie własnych reguł dla testów stylu, ale sposób ich specyfikacji jest uciążliwy
i nieintuicyjny. W tym kontekście warto zwrócić uwagę na to, że pracochłonność przygotowania
testów w systemach zautomatyzowanej weryfikacji była i często nadal pozostaje poważną przeszkodą w ich praktycznym zastosowaniu (por. Carter et al., 2003).
W zrealizowanym w PJATK Systemie Doskonalenia Kwalifikacji Programistycznych (SDKP)
przygotowano narzędzia automatycznej weryfikacji stylu programowania, które odróżniają
się od znanych rozwiązań możliwością ustalania przez dydaktyków własnych reguł testowania w bardzo różnorodny (dostosowany do ich wiedzy, umiejętności i upodobań, a także
możliwie najbardziej właściwy dla treści zadań) sposób. Charakterystyczną cechą SDKP jest
również łatwość specyfikacji testów stylu, zmniejszająca pracochłonność tworzenia testów przez
dydaktyków.
Testowanie stylu rozwiązań może odbywać się za pośrednictwem trzech modułów SDKP.
Moduł „weryfikacji spełniania wymagań” (ReqTest) służy do testowania dowolnych wymagań
postawionych wobec kodu rozwiązania zleconego zadania. Przy definiowaniu takich wymagań
dydaktycy mogą posługiwać się zarówno bardzo prostymi i łatwymi w użyciu środkami
(wyrażenia regularne), jak i bardziej zaawansowanymi sposobami analizy składniowej przy
użyciu skryptów, działających na kodach źródłowych. W skryptach tych dydaktyk stosować
może dowolne narzędzia programistyczne, od prostych działań na napisach, po przetwarzanie drzew składniowych (AST – Abstract Syntax Tree) programów. Drugi moduł – „testowanie
stylu” (StyleTest) – koncentruje się na zastosowaniu predefiniowanych reguł i metryk, sprawdzających wybrane elementy właściwego stylu programowania. W tym celu z SDKP zintegrowany
został znany system statycznej analizy kodu – PMD (strona główna projektu PMD, http://pmd.
sourceforge.net), przy czym integracja ta charakteryzuje się ułatwieniami specyfikacji testów,
znacząco obniżającymi pracochłonność ich przygotowania przez dydaktyków. Dodatkowo
EduAkcja. Magazyn edukacji elektronicznej, nr 2 (10)/2015 , str. 40
Krzysztof Barteczko, Weryfikacja stylu programowania w rozwiązaniach zadań...
moduł ten umożliwia dydaktykom łatwe dostosowanie istniejących reguł do swoich potrzeb,
a także łatwe tworzenie nowych reguł w konwencji PMD. Wreszcie moduł trzeci – „scenariuszy behawioralnych” (BehaviorTest) – którego głównym zadaniem jest testowanie poprawności
rozwiązań, pozwala też na weryfikację stylu i wymagań za pomocą narzędzi tzw. refleksji (Java
Reflection API). Jest to analiza dynamiczna (realizowana poprzez wykonanie kodów rozwiązań
studenckich) i w niektórych przypadkach może mieć przewagę nad analizą statyczną, zapewnianą przez moduły ReqTest i StyleTest.
Jak podaje Cristina Lopes (Lopes, 2014), styl programowania to nic innego, jak zastosowanie
określonych ograniczeń przy tworzeniu kodu. Zatem wszystkie wyżej wymienione moduły mogą
być używane przy weryfikacji stylu kodowania rozwiązań, zarówno selektywnie, jak i łącznie.
Dydaktyk może więc dobierać sposoby analizy kodu właściwe dla konkretnego zadania oraz
zgodne z jego umiejętnościami, przy czym w większości przypadków definiowanie testów nie
jest pracochłonne.
W kolejnych punktach artykułu, po ogólnej informacji o systemie SDKP, przedstawione
zostaną sposoby testowania stylu we wspomnianych wyżej modułach.
2. O Systemie Doskonalenia Kwalifikacji Programistycznych
System Doskonalenia Kwalifikacji Programistycznych (SDKP) zrealizowany został w Polsko­
Japońskiej Akademii Technik Komputerowych w ramach projektu POKL „Informatyk – wszechstronny specjalista”.
Podstawowym celem SDKP było stworzenie modelu analizy i oceny postępów studentów,
a także zrozumiałego, wspomagającego proces uczenia się, przedstawiania im wyników tej oceny
(Barteczko, 2013).
Realizacja tego zamierzenia opierała się w głównej mierze na specjalnie przygotowanych
narzędziach weryfikacji rozwiązań zadań programistycznych w języku Java pod kątem:
• poprawności programu,
• spełniania postawionych w zadaniu wymagań oraz stylu programowania,
• efektywności, uniwersalności, elastyczności, skalowalności rozwiązań.
Poprawność programu może być sprawdzana za pomocą testów wykonania (RunTest),
w których weryfikowane są wyniki działania programu lub scenariuszy behawioralnych
(BehaviorTest), w których używa się szczegółowych specyfikacji odnośnie do zachowania
programu i jego części (klas, metod).
Spełnianie wymagań i styl programowania weryfikowane są za pomocą wspomnianych już
modułów ReqTest i StyleTest, a także specjalnego zastosowania modułu BehaviorTest.
Testy efektywności są szczególnym przypadkiem testów poprawności, w których zadaje się
ograniczenia na czas wykonania programu i porównuje ten czas z wzorcowym.
Uniwersalność, elastyczność, skalowalność rozwiązań testowana jest za pomocą odpowiednich definicji testów dla modułów RunTest, ReqTest i StyleTest.
Weryfikacja rozwiązań studentów wymaga przygotowania odpowiednich definicji zadań.
Strukturę definicji schematycznie przedstawia Rys. 2.
EduAkcja. Magazyn edukacji elektronicznej, nr 2 (10)/2015 , str. 41
Krzysztof Barteczko, Weryfikacja stylu programowania w rozwiązaniach zadań...
Rysunek 2. Schemat struktury definicji zadania
Widoczne na Rys. 2 wymagania na strukturę katalogową, nazewnictwo plików, a nawet ich
(częściową) zawartość są niezbędne dla wielu rodzajów testów (również dla niektórych testów
stylu, realizowanych za pomocą modułu ReqTest). Spełnienie tych wymagań musi być sprawdzane przed uruchomieniem testów. Zajmuje się tym specjalny moduł Projector. Stosowany jest
on w trzech różnych fazach działania systemu:
• w fazie pobierania schematów projektów przez studentów (wtedy generowany jest projekt
o właściwej dla danego zestawu zadań – laboratorium – zawartości, tak by ułatwić studentom
dostosowanie do formalnych wymagań zadania),
• w fazie dostarczania przez studentów rozwiązań (wtedy wykrywane są błędy formalne, a studenci są o nich informowani i mogą je poprawić),
• w fazie testowania (wtedy wykryte błędy formalne zapisywane są do bazy wyników testów).
W szczególności za pomocą modułu Projector sprawdzane jest:
• czy w projektach studentów zawarte są wszystkie wymagane pakiety, klasy, katalogi,
• czy w odpowiednich klasach znajduje się metoda main (jeśli takie wymaganie postawiono
w zadaniu),
• czy student nie dokonał niedozwolonych modyfikacji kodu utworzonego przez system (niektóre – oczywiście nie wszystkie – zadania mogą zawierać gotowe kody, których w całości lub
częściowo nie wolno zmienić).
Ogólną procedurę testowania rozwiązań przedstawiono na Rys. 3.
EduAkcja. Magazyn edukacji elektronicznej, nr 2 (10)/2015 , str. 42
Krzysztof Barteczko, Weryfikacja stylu programowania w rozwiązaniach zadań...
Rysunek 3. Schemat procedury testowania rozwiązań
Jak pokazano na Rys. 3, rozwiązania studentów są rozpakowywane (extract), po czym sprawdzana jest (check) ich formalna poprawność oraz wykonywana kompilacja programów. Dla
rozwiązań bez błędów formalnych i błędów kompilacji – zgodnie z definicją zadania – wykonywane są określone w niej testy (execute). Wyniki uzyskiwane w postaci mapy błędów (errMap)
zapisywane są do bazy danych, po czym po przetworzeniu do bardziej werbalnej postaci,
udostępniane dydaktykom (do ewentualnego komentowania) i studentom (z ewentualnymi
komentarzami dydaktyków).
3. Moduł weryfikacji spełniania wymagań
Moduł weryfikacji spełniania wymagań – ReqTest – pozwala na stwierdzenie, czy w kodzie
rozwiązania występują określone konstrukcje. Ma to ogólniejszy – niż tylko sprawdzanie
dobrego stylu programowania – wymiar. Może służyć zadaniom, w których student wykazać
się musi nie tyle dobrym stylem programowania, ale pomysłowością – na przykład „napisz taki
a taki program, nie używając instrukcji sterujących”. Najczęściej jednak moduł ten jest używany
do stosunkowo łatwego uzyskiwania odpowiedzi na pytania, ściśle związane ze stylem i jakością
kodu: czy użyto właściwych konstrukcji językowych? czy zastosowano właściwe biblioteki
i metody? czy zastosowano właściwe sposoby programowania?
Spełnianie wymagań jest testowane za pomocą:
• list wyrażeń regularnych (regex), za pomocą których można sprawdzić, czy w podanych źródłach występują określone fragmenty tekstu,
albo:
• skryptów, które uzyskują teksty kodów źródłowych (lub ich AST) i mogą analizować je przy
EduAkcja. Magazyn edukacji elektronicznej, nr 2 (10)/2015 , str. 43
Krzysztof Barteczko, Weryfikacja stylu programowania w rozwiązaniach zadań...
użyciu różnych metod przetwarzania tekstów (lub AST kodu).
Listy wyrażeń regularnych podane są w sekcji data testów wymagań definicji zadania
i następnie wykorzystywane przez moduł testujący. W zależności od ustawień, testowanie może
dotyczyć wszystkich lub wybranych plików źródłowych, z uwzględnieniem lub bez uwzględnienia białych znaków, komentarzy i literałów napisowych, a także z różnymi opcjami tzw. tokenizacji (podziału tekstu na symbole).
Sposób zapisu testów wymagań w definicji zadania z użyciem wyrażeń regularnych jest prosty
i dość werbalny, co obrazuje przykład na Rys. 4.
Rysunek 4. Przykładowa specyfikacja testowania wymagań w definicji zadania
W przedstawionej na Rys. 4 specyfikacji wprowadzono sekcję data, w której podano kolejno
testowane przypadki (przypadek 1: w kodach rozwiązania musi występować tekst ListModel;
przypadek 2: w kodach nie może występować tekst DefaultListModel). To oczywiście uproszczony przykład, ilustrujący tylko składnię definicji testu z użyciem wyrażeń regularnych.
Często jednak tylko za pomocą prostych wyrażeń regularnych możemy uzyskać praktycznie
użyteczne wyniki. Sprzyja temu tokenizacja kodów rozwiązań (czyli podział kodu na symbole),
np. usuwająca białe znaki, komentarze i literały napisowe, a także ewentualne łączenie wymagań
odnośnie do obowiązkowej zawartości wybranego pliku ze specyfikacją testu wymagań.
Dla przykładu rozważmy zadanie, w którym student powinien zdefiniować odpowiednie klasy,
w taki sposób, by podany program (zob. Kod 1) wykonał się i wyprowadził odpowiednie wyniki.
public class Test {
public static void main(String[] args) {
Pacjent[] pacjenci = { new ChoryNaGlowe("Janek"),
new ChoryNaNoge("Edzio"),
new ChoryNaDyspepsje("Marian")
};
}
}
for (Pacjent p : pacjenci) {
System.out.println("Pacjent:
" + p.nazwisko() + '\n' +
"Chory na:
" + p.choroba() + '\n' +
"Zastosowano: " + p.leczenie() +"\n\n"
);
}
Kod 1. Przykładowa zawartość obowiązkowej klasy w rozwiązaniach studentów
EduAkcja. Magazyn edukacji elektronicznej, nr 2 (10)/2015 , str. 44
Krzysztof Barteczko, Weryfikacja stylu programowania w rozwiązaniach zadań...
Przy tym wymagane jest, aby w rozwiązaniu w sposób właściwy zastosowano polimorficzne
wywołania metod choroba() i leczenie(). Można to łatwo stwierdzić, sprawdzając, czy w klasie
Pacjent dostarczono (ew. abstrakcyjnych) metod choroba() i leczenie(), a w klasach pochodnych
przedefiniowano je lub zaimplementowano (Kod 2)
test:
req:
parse: true
# usuwa białe znaki, komentarze i literały napisowe
data:
- caseMsg: [ -2, 'Brak polimorfizmu w wołaniu metody choroba()']
contains:
- file: Pacjent
regex: 'Stringchoroba\(\)(\{)|;'
- file: ChoryNaNoge
regex: 'Stringchoroba\(\)\{'
- file: ChoryNaGlowe
regex: 'Stringchoroba\(\)\{'
- file: ChoryNaDyspepsje
regex: 'Stringchoroba\(\)\{'
Kod 2. Czy metoda choroba() jest przedefiniowana?
Dostępne są różne opcje tokenizacji (w powyższych przykładach wartości pod kluczem parse),
pozwalające na traktowanie określonych znaków jako składowych wyłuskiwanych symboli.
Pokazana wcześniej najprostsza opcja (parse: true) usuwa z kodu wszystkie białe znaki, literały
napisowe i komentarze. Natomiast opcja parse: word spowoduje:
• pominięcie białych znaków, komentarzy i literałów napisowych,
• traktowanie ciągów znaków dozwolonych w identyfikatorach języka jako składowych jednego
symbolu,
• traktowanie innych znaków jako pojedynczych symboli,
• zwrócenie (jako wyniku tokenizacji) ciągu symboli, rozdzielonych spacjami.
Inne opcje pozwalają na podanie dodatkowych znaków, które mają być traktowane jako
składowe symboli. Dzięki różnorodności opcji tokenizacji możliwe jest znacznie łatwiejsze
budowanie wyrażeń regularnych, dostosowanych do konkretnych testów. Na przykład, przy
użyciu opcji parse: word, po to by stwierdzić, czy w kodzie występuje instrukcja if, wystarczy
użyć wyrażenia regularnego <spacja>if<spacja>.
Gdy same wyrażenia regularne nie są wystarczające do weryfikacji postawionych w zadaniu
wymagań, dydaktyk może użyć skryptu testującego. Skrypty zapisywane są w języku Groovy,
którego składnia i semantyka zawiera elementy znacząco ułatwiające pisanie programów (strona
główna platformy Groovy, http://groovy-lang.org). Język Groovy pozwala również na bezpośrednie stosowanie składni języka Java, co oznacza, że dydaktyk może „z miejsca” programować
skrypty testujące, ewentualnie stopniowo, w miarę poznawania języka Groovy, wprowadzając
w nich upraszczające program elementy składniowe.
Rozważmy ilustracyjny przykład. Niech wymaganiem wobec rozwiązania umieszczonego
w pliku Program.java będzie, aby liczba instrukcji w każdej z klauzul catch (obsługujących
wyjątki) nie przekraczała 5. Tego nie da się sprawdzić prostym wyrażeniem regularnym, wobec
tego zastosowanie skryptu jest tu jak najbardziej na miejscu.
Skrypt zapisany będzie w języku Groovy i umieszczony bezpośrednio pod kluczem script
w opisie przypadku testowego (Kod 3).
EduAkcja. Magazyn edukacji elektronicznej, nr 2 (10)/2015 , str. 45
Krzysztof Barteczko, Weryfikacja stylu programowania w rozwiązaniach zadań...
test:
req:
data:
- caseMsg: [-2, 'liczba instrukcji w catch przekracza dozwolone max = 5']
file: Program
parse: word
script: |
def regex = / catch \(.+?\) \{(.*?)\}/
def clist = src.findAll(regex) { allMatch, group1 -> group1 }
for ( kod in clist ) if (kod.count(';') > 5) return false
return true
Kod 3. Ilustracyjny skrypt weryfikujący spełnienie wymagania
W skrypcie dostępna jest zmienna src, oznaczająca stokenizowany kod źródłowy pliku.
Metoda:
src.findAll(regex) { domknięcie }
wobec wszystkich fragmentów src, pasujących do wzorca regex, stosuje kolejno podane
domknięcie z argumentami allMatch (całe dopasowanie) i group1 (zawartość pierwszej grupy
w dopasowaniu). Kolejne wyniki zwracane przez domknięcie (w powyższym przykładzie zawartości pierwszej grupy w dopasowaniu) są wstawiane do nowo utworzonej listy, która ostatecznie jest rezultatem wywołania metody. W konsekwencji elementy listy clist to kody kolejnych
klauzul catch i w łatwy sposób można określić liczbę instrukcji, występujących w każdym z tych
kodów. Skrypt zwraca wartość true, jeśli w żadnej klauzuli catch liczba instrukcji nie przekracza
5 (wymaganie jest spełnione) i false w przeciwnym razie. W tym ostatnim przypadku punktacja
za rozwiązanie zadania zostanie obniżona o 2 punkty, a użytkownicy (studenci, dydaktyk)
uzyskają odpowiedni opis błędu.
W SDKP rodzaje i sposoby zapisu skryptów testowania wymagań są różnorodne: skrypty mogą
zwracać nie tylko wartości boolowskie, ale i konkretne listy błędów, mogą otrzymywać różne
dodatkowe informacje i działać nie tylko na pojedynczych plikach, ale i na całych zestawach
kodów. Mogą być też zapisywane w kolekcjach skryptów w bazach danych, a z poziomu definicji
testów takie utrwalone skrypty można w naturalny sposób przywoływać do wykonania.
Szczegółowe przedstawienie środowiska skryptowej weryfikacji spełniania wymagań to temat
na odrębny artykuł. Tu warto może jeszcze tylko wspomnieć, że w skryptach można stosować
analizę drzewa składniowego kodów źródłowych (AST). W tym celu z SDKP zintegrowano
bibliotekę typu open-source o nazwie JavaParser (https://github.com/javaparser/javaparser).
Jest to narzędzie „lekkie” i proste w użyciu. I choć brakuje mu pewnych funkcjonalności, to
może być przydatne w sytuacjach, gdy inne rodzaje analizy składniowej w skryptach zawodzą
lub ich oprogramowanie jest uciążliwe.
Przykładowy skrypt, tym razem zapisany w składni języka Java, działa na AST kodu źródłowego z podanego pliku (Program.java) i sprawdza czy liczba wywołań metod w tym kodzie
nie przekracza 30. W kodzie skryptu (Kod 4) jako src będzie dostępne AST kodu źródłowego,
a w przetwarzania informacji wykorzystano, często stosowany w analizach AST, mechanizm
wzorca projektowego Visitor (odwiedzający, wizytator (Martin, 2002)).
test:
req:
data:
- caseMsg: [-2, 'liczba wywołań metod > 30']
file: Program*AST
script: kod_skryptu | id_skryptu_w_BD
EduAkcja. Magazyn edukacji elektronicznej, nr 2 (10)/2015 , str. 46
Krzysztof Barteczko, Weryfikacja stylu programowania w rozwiązaniach zadań...
Kod skryptu:
class V extends VoidVisitorAdapter {
static int count = 0
public void visit(MethodCallExpr exp, Object data) {
count++;
super.visit(exp, data);
}
}
new V().visit(src, null);
return !(V.count > 30);
Kod 4. Przykładowy skrypt, stosujący wizytowanie drzewa AST
4. Testowanie spełniania wymagań za pomocą scenariuszy behawioralnych
Moduł scenariuszy behawioralnych (BehScenarios) służy przede wszystkim do testowania
poprawności programów poprzez sprawdzanie czy poszczególne elementy programu (takie jak
np. metody jakiejś klasy) działają we właściwy sposób.
W SDKP odbywa się to w kategoriach testowania zachowania programu z użyciem scenariuszy, wykonywanych w zintegrowanym z systemem środowisku testowania behawioralnego
easyb (projekt easyb, http://easyb.org, https://github.com/easyb). Scenariusze behawioralne
tworzone są przy użyciu DSL (domain specific language – wyspecjalizowanego języka dziedzinowego) easyb, który wprowadza odpowiednią strukturę testów np.
scenario "Wypłaty z konta"
given "Stan konta" {...}
when "Wypłacono dopuszczalną kwotę" { ...}
then "Stan konta zmniejszył się o tę kwotę"
{ ... }
when "Próba wypłaty niedozwolonej lub za dużej kwoty" {
...
}
then "Sygnalizacja błędu" { .... }
and "Stan konta nie zmienił się" { ...}
a w klauzulach given, when i then pozwala stosować dowolne konstrukcje języka Groovy,
a także ułatwienia DSL easyb.
Za pomocą takiego scenariuszowego podejścia można testować nie tylko poprawność
programu, ale również spełnianie postawionych wobec kodu wymagań. Wystarczy w klauzulach testujących (given, when i then) zastosować metody tzw. refleksji. Refleksja oznacza m.in.
możliwość uzyskiwania w trakcie działania programu pełnej informacji o charakterystykach
klasy (m.in. o dziedziczeniu, polach, konstruktorach, metodach itp., The Reflection API, https://
docs.oracle.com/javase/tutorial/reflect) .
Z użyciem metod refleksji można np. zbudować scenariusz, sprawdzający, czy jakaś klasa (tu
klasa C) bezpośrednio lub pośrednio dziedziczy klasę java.util.ArrayList (Kod 5).
scenario "Test (bez)posredniego dziedziczenia",{
when "Mamy klasę C z konstruktorem bezparametrowym@0", {
try {
c = new C();
} catch(Exception exc) {
fail '- okazuje się, że program nie spełnia tego założenia'
EduAkcja. Magazyn edukacji elektronicznej, nr 2 (10)/2015 , str. 47
Krzysztof Barteczko, Weryfikacja stylu programowania w rozwiązaniach zadań...
}
}
then "klasa powinna dziedziczyć bezpośrednio lub pośrednio ArrayList@-2", {
def claz = c.getClass().superclass
def failed = true
while (claz && claz != java.lang.Object.class) {
if (claz == java.util.ArrayList.class) {
failed = false
break
}
claz = claz.superclass
}
if (failed) fail 'ale nie dziedziczy'
}
}
Kod 5. Testowanie wymagań w scenariuszach behawioralnych
W przypadku niespełnienia wymagań wykonywana jest instrukcja DSL fail, a easyb generuje
odpowiednie komunikaty i testowanie jest zakończone. System SDKP odczytuje wyniki scenariuszy, zmniejsza punktację (zmniejszenia są podane w opisach klauzul when i then po znaku @,
przy czym wartość 0 oznacza odjęcie całości punktów), a następnie tworzy raporty, przetwarzając i łącząc w odpowiedni sposób komunikaty wygenerowane przez easyb. Przykładową postać
takiego raportu przedstawiono poniżej.
Laboratorium: 3, zadanie: 2
Maksimum punktów do uzyskania: 6
_________________________________________________________________________
Indeks: xxxxx
Autor: Nazwisko Imię
Uzyskane punkty: 4
_________________________________________________________________________
Wyniki testów zachowania - zmniejszenie punktacji = -2
GDY: Mamy klasę C z bezparametrowym konstruktorem
- klasa powinna dziedziczyć bezpośrednio lub pośrednio ArrayList
( ale nie dziedziczy )
> zmniejszenie punktacji = -2
Analiza dynamiczna z użyciem metod refleksji w niektórych przypadkach może mieć
przewagę nad analiza statyczną, w tym wykorzystującą AST. Będzie tak na pewno zawsze wtedy,
gdy sprawdzane jest użycie konkretnych typów, o których decyduje JVM w fazie wykonania
programu. Przykładowo, jeżeli jakaś metoda zwraca wynik podtypu ogólniejszego typu w zależności od kontekstów wykonania, to tylko analiza dynamiczna może zidentyfikować ten podtyp.
Refleksja w scenariuszach behawioralnych jest też pomocna, gdy zdefiniowanie testu spełnienia
wymagań środkami refleksji jest łatwiejsze niż zastosowanie analizy AST.
5. Moduł weryfikacji stylu
Do automatycznej oceny stylu programowania powszechnie używa się gotowych środowisk
i programów statycznej analizy kodu. Przy projektowaniu SDKP rozważano zastosowanie
platformy SonarQube (strona główna platformy SonarQube, http://www.sonarqube.org), stosoEduAkcja. Magazyn edukacji elektronicznej, nr 2 (10)/2015 , str. 48
Krzysztof Barteczko, Weryfikacja stylu programowania w rozwiązaniach zadań...
wanej w firmach IT do analizy jakości kodu. Uznano jednak, że jej koncentracja na graficznych
interfejsach, wspomagających prowadzenie analiz dla dużych projektów informatycznych, nie
odpowiada dobrze celom projektu. Wobec tego wzięto pod uwagę zintegrowanie z systemem
SDKP jednego z kilku prostych w użyciu, darmowych narzędzi typu open-source, takich jak
Checkstyle (strona główna projektu Checkstyle, http://checkstyle.sourceforge.net), FindBugs
(strona główna projektu Find-Bugs, http://findbugs.sourceforge.net) czy PMD (strona główna
projektu PMD, http://pmd.sourceforge.net).
Za ich pomocą można automatycznie analizować kody rozwiązań m.in. pod kątem:
• powtórzenia fragmentów (spełnienie zasady „DRY” – Don’t Repeat Yourself) (tylko w PMD),
• niepotrzebnych fragmentów (np. deklaracji nieużywanych zmiennych),
• błędów, które mogą nie być sygnalizowane w testach poprawności (np. użycie operatora ==
w porównaniu napisów),
• złych praktyk (np. ignorowania wyjątków),
• konwencji nazewniczych i stylu programowania (nazwy zbyt krótkie, zbyt długie, niezgodne
z zasadami języka; wadliwe formatowanie kodu),
• złożoności, czytelności, elastyczności, uniwersalności kodu (z zastosowaniem rozlicznych
metryk).
Wybór padł na PMD, głównie dlatego, że w momencie tworzenia modułu testowania stylu
(w pierwszej połowie 2014 r.) było to jedyne narzędzie, obsługujące nową wersję języka Java
(wersję 8). Ponadto działanie PMD opiera się na przetwarzaniu zewnętrznie definiowanych
reguł, a bogatą listę gotowych już reguł można w łatwy sposób modyfikować (zmieniać właściwości wybranych reguł) oraz rozszerzać (dodawać nowe, własne reguły).
Reguły w PMD zapisywane są w postaci XML, co nie jest wygodne. Dlatego dla potrzeb SDKP
opracowano oryginalną specyfikację reguł, która pozwala w bardzo łatwy i przejrzysty sposób
odwoływać się do predefiniowanych reguł, zmieniać ich właściwości i dodawać nowe reguły.
Ogólnie, specyfikacja ta ma następującą postać:
test:
styl: string | mapa
gdzie:
string = rozdzielone przecinkami lub spacjami nazwy zdefiniowanych
reguł lub zbiorów reguł, dodatkowo może być podane
słowo dry – ustawia testy powtórzeń kodu
mapa =
[dry:] // mapa parametrów wykrywania powtórzeń
exclude_pattern: // lista pomijanych plików
lista_map_referencji_do_reguł | definicja_nowej_reguły_ad_hoc
PMD zwiera bogaty zestaw predefiniowanych reguł, podzielonych pomiędzy różnymi
zbiorami reguł (Rys.5).
Rysunek 5. Zbiory reguł i reguły PMD
EduAkcja. Magazyn edukacji elektronicznej, nr 2 (10)/2015 , str. 49
Krzysztof Barteczko, Weryfikacja stylu programowania w rozwiązaniach zadań...
Zatem przykładowe odwołanie w najprostszej specyfikacji testów stylu może mieć następującą postać:
test:
styl: ‘basic codesize/ExcessiveMethodLength naming’
co oznaczać będzie zastosowanie wszystkich reguł zbioru basic (dobre praktyki programowania) i naming (konwencje nazewnicze) oraz reguły ExcessiveMethodLength (kontrola rozmiaru
kodu metody) ze zbioru codesize.
Użycie map referencji do reguł w specyfikacji testów stylu pozwala zmieniać właściwości
reguł (Kod 6).
styl:
ref:
- name: naming
exclude: ShortClassName
exclude: ShortVariable
- name: naming/ShortVariable
message: "Unikaj krótkich nazw zmiennych, jak {0}"
- name: naming/ShortClassName
message: 'Nazwa klasy winna mieć długość > 2'
property:
- xpath
- |
//ClassOrInterfaceDeclaration[string-length(@Image) < 3]
- name: codesize/TooManyMethods
property: [ maxmethods, 2 ]
Kod 6. Użycie map referencji do reguł
Ta specyfikacja oznacza:
• zastosowanie wszystkich reguł zbioru naming z wyłączeniem reguł podanych w exclude,
• modyfikację komunikatu o naruszeniu reguły naming/ShortVariable (w tym komunikacie
w miejsce {0} będą podstawiane te nazwy zmiennych, które naruszają regułę),
• użycie reguł naming/ShortClassName i codesize/TooManyMethods ze zmienionymi właściwościami (nazwa klasy winna składać się z co najmniej trzech znaków, definicja klasy nie powinna zawierać więcej niż dwie metody).
Warto tu zwrócić uwagę na właściwość xpath podaną przy redefinicji reguły naming/
ShortClassName. PMD dla analizy kodu buduje jego drzewo składniowe (AST), a w definicjach
reguł pozwala na użycie języka XPath (XML Path Language), służącego ogólnie do nawigowania po XML i uzyskiwania informacji o elementach dokumentu XML (Kay, 2004). Węzły AST
oraz ich różne właściwości mogą być odwzorowane w XML, a zatem XPath staje się wygodnym
narzędziem uzyskiwania informacji o różnych częściach drzewa składniowego programu.
Zapisany w języku XPath warunek:
//ClassOrInterfaceDeclaration[string-length(@Image) < 3]
spowoduje pobranie listy wszystkich deklaracji klas i interfejsów z AST i raportowanie przypadków, gdy nazwa klasy/interfejsu ma długość mniejszą od 3 znaków.
W SDKP można za pomocą XPath definiować całkiem nowe reguły, zapisywać je w bazie
danych, po czym używać w specyfikacjach testów stylu. Można też zapisywać w bazie danych
nowe reguły, oparte na referencjach do innych reguł z modyfikacją ich właściwości.
Przykładowa definicja nowej reguły, wymagającej, by wszystkie klasy programu dziedziczyły
ArrayList, może być zapisana w bazie danych pod kluczem S_EXTENDS_ARRAYLIST:
_id: S_EXTENDS_ARRAYLIST
xpath: |
//ExtendsList/ClassOrInterfaceType[@Image!=‘ArrayList‘]
EduAkcja. Magazyn edukacji elektronicznej, nr 2 (10)/2015 , str. 50
Krzysztof Barteczko, Weryfikacja stylu programowania w rozwiązaniach zadań...
a zapis w bazie danych pod kluczem S_ONEFIELD zmodyfikowanej reguły PMD codesize/
TooManyFields może mieć następującą formę:
_id: S_ONEFIELD
ref:
- name: codesize/TooManyFields
message: 'W klasie winno być tylko jedno pole'
property: [ maxfields, 1 ]
Ważną rolę w testowaniu stylu odgrywa moduł SDKP o nazwie RuleSetManager. Zapewnia
on przekształcenie prostej, klarownej specyfikacji testów stylu na wymagane przez PMD pliki
XML, uwzględniając przy tym odwołania do reguł zapisanych wcześniej w bazach danych SDKP.
W ten sposób moduł ten tworzy odpowiednie środowisko do uruchomienia PMD (Rys. 6).
Rysunek 6. RuleSetManager przygotowuje środowisko testowania stylu
Wprowadzone w SDKP konwencje dla zapisu reguł testowania stylu oraz moduł
RuleSetManager, wykonujący „czarną robotę” wiązania reguł i przekształcania ich zapisu do
XML, istotnie zwiększają komfort pracy dydaktyka.
Korzystając z tych udogodnień, dydaktyk może łatwo i szybko zapisać definicję testu zamiast
tracić czas na tworzenie rozbudowanych plików XML.
Warto przy okazji zauważyć, że definicje dla testów stylu – tak jak wszystkie inne definicje
w SDKP – korzystają ze składni języka YAML (The Official YAML Website, http://yaml.
org), który – jako nadzbiór notacji JSON – jest od dawna utrwalonym standardem, chętnie
używanym przez profesjonalnych developerów. Jednolitość form specyfikacji wszystkich testów
SDKP w dobrze zdefiniowanym i opisanym standardzie także stanowi czynnik ułatwiający pracę
dydaktykom.
Samo testowanie stylu polega na uruchomieniu zewnętrznego procesu PMD w odpowiednio przygotowanym środowisku, a następnie przejęciu, przekształceniu i zapisie wyników jego
działania do bazy danych (Rys. 7).
EduAkcja. Magazyn edukacji elektronicznej, nr 2 (10)/2015 , str. 51
Krzysztof Barteczko, Weryfikacja stylu programowania w rozwiązaniach zadań...
Rysunek 7. Procedura testowania stylu
Przy interpretacji wyników używana jest część specyfikacji testów stylu, która określa zmniejszenie punktacji za naruszenie określonych reguł. Przy tym ogólna definicja zadania pozwala
określić, czy to zmniejszenie punktacji „za styl” będzie wpływało na ogólną ocenę rozwiązania
studenta czy też nie.
6. Podsumowanie
Analiza i ocena stylu programowania odgrywa ważną rolę w dydaktyce przedmiotów programistycznych. Przy większej liczbie rozwiązań, szczególnie w zdalnym nauczaniu, analiza i ocena
winny być wspierane przez narzędzia automatycznej weryfikacji stylu. I choć w wielu systemach
automatycznej oceny rozwiązań zadań programistycznych problematyka ta znajduje swoje
odzwierciedlenie, to praktyczne stosowanie takich narzędzi często natrafia na barierę niemożności zdefiniowania przez dydaktyka własnych testów, odpowiednich dla jego zadań, lub trudności
i pracochłonności przygotowania takich definicji.
W zrealizowanym w Polsko-Japońskiej Akademii Technik Komputerowych Systemie
Doskonalenia Kwalifikacji Programistycznych (SDKP) zaproponowano narzędzia automatycznej weryfikacji stylu programowania, które wyróżniają się wśród znanych rozwiązań możliwością ustalania przez dydaktyków reguł testowania w bardzo różnorodny (dostosowany do
ich wiedzy, umiejętności i upodobań, a także możliwie najbardziej właściwy dla treści zadań)
sposób. Charakterystyczną cechą SDKP jest również łatwość specyfikacji testów, zmniejszająca
pracochłonność tworzenia testów przez dydaktyków.
W zakresie oceny stylu programowania SDKP oferuje:
• narzędzia do weryfikacji spełniania wymagań wobec rozwiązań zadań programistycznych
poprzez:
–– budowę i użycie wyrażeń regularnych,
–– tworzenie lub użycie utrwalonych skryptów testujących w językach Java i/lub Groovy,
–– tworzenie lub użycie scenariuszy w zintegrowanym z systemem środowisku testowania
behawioralnego easyb, dostarczającego niewielkiego i łatwego w użyciu DSL (domain specific language) do budowy scenariuszy,
• narzędzia testowania stylu oparte na:
–– predefiniowanych regułach środowiska PMD,
–– własnych modyfikacjach tych reguł przez dydaktyków,
–– tworzeniu nowych reguł dla środowiska PMD.
Sposób definiowania testów przez dydaktyków jest prosty, intuicyjny i jednolity, bo bazujący
EduAkcja. Magazyn edukacji elektronicznej, nr 2 (10)/2015 , str. 52
Krzysztof Barteczko, Weryfikacja stylu programowania w rozwiązaniach zadań...
na składni standardu YAML.
Oferowane w SDKP różnorodne, bogate w możliwości, łatwe w użyciu środki wspomagające
weryfikację stylu programowania:
• mogą być używane wymiennie, wedle umiejętności i gustu dydaktyka, a także w możliwie
najlepszym dostosowaniu do konkretnych zadań programistycznych,
• mogą być też używane łącznie, wzajemnie się uzupełniając i ułatwiając ocenę.
Bogactwo możliwości oraz łatwość tworzenia i użycia testów stylu powinno sprzyjać miarodajności ocen i zachęcać dydaktyków do używania narzędzi automatycznej weryfikacji stylu
programowania w praktyce.
7. Bibliografia
1. Ala-Mutka, K. (2005). A survey of automated assessment approaches for program-ming assignments. Computer
Science Education, 15(2).
2. Ala-Mutka, K., Uimonen, T., Jarvinen, H. M. (2004). Supporting students in C++ programming courses with automatic program style assessment. Journal of Infor-mation Technology Education, 3.
3. Barteczko, K. (2012). Zadania programistyczne w zdalnym nauczaniu, EduAkcja, Magazyn Edukacji Elektronicznej,
1(3).
4. Barteczko, K. (2013). Założenia Systemu Doskonalenia Kwalifikacji Programistycz-nych w ramach zdalnego nauczania, Warszawa: Wydawnictwo PJWSTK.
5. Caiza, J. C., Del Alamo, J. M. (2013). Programming assignments automatic grading: Review of tools and implementations, Inted 2013 Proceedings
6. Carter, J., English, J., Ala-Mutka, K., Dick, M., Fone, W., Fuller, U., Sheard, J. (2003). How shall we assess this? ACM
SIGCSE Bulletin, 35(4)
7. Douce, C., Livingstone, D., Orwell, J. (2005). Automatic test-based assessment of programming: A review. ACM Journal of Educational Resources in Computing, 5(3)
8. Green, R. (2000-2014). Unmaintainable code. Pobrano 12.04.2015, z:http://www.mindprod.com/jgloss/unmain.html
9. Hunt, A., Thomas, D. (1999). The Pragmatic Programmer: From Journeyman to Mas-ter. Addison Wesley.
10. Kay, M. (2004). XPath 2.0 Programmer’s Reference. Willey.
11. Kernighan, B. W, Plauger, P. J. (1978). The Elements of Programming Style, 2nd Edition. McGraw Hill.
12. Lopes, C. V. (2014). Exercises in Programming Style. Chapman and Hall/CRC.
13. Martin, R. C. (2002). Agile Software Development, Principles, Patterns, and Practices. Prentice Hall.
14. Martin, R. C. (2008). Clean Code: A Handbook of Agile Software Craftsmanship. Prentice Hall.
15. Striewe, M., Goedicke, M. (2014). A Review of Static Analysis Approaches for Programming Exercises, Proceedings of
International Computer Assisted Assessment (CAA) Conference 2014, Zeist.
16. Striewe, M., Goedicke, M., Balz, M. (2008). Computer Aided Assessments and Pro-gramming Exercises with JACK.
Technical Report 28, ICB, University of Duisburg-Essen.
17. Weinberg, G. M. (2011). The Psychology of Computer Programming: Silver Anni-versary eBook Edition. Weinberg
& Weinberg.
18. Platforma. Groovy. (b.d.). Pobrano 12.04.2015, z:http://groovy-lang.org
19. Platforma SonarQube. (b.d.). Pobrano 12.04.2015, z: http://www.sonarqube.org
20. Projekt Checkstyle. (b.d.). Pobrano 12.04.2015, z: http://checkstyle.sourceforge.net
21. Projekt FindBugs. (b.d.). Pobrano 12.04.2015, z: http://findbugs.sourceforge.net
22. Projekt PMD. (b.d.). Pobrano 12.04.2015, z: http://pmd.sourceforge.net
23. JavaParser. (b.d.). Pobrano 12.04.2015, z: https://github.com/javaparser/javaparser
24. Projekt easyb. (b.d.). Pobrano 12.04.2015, z: http://easyb.org, https://github.com/easyb
25. The Reflection API. (b.d). Pobrano 12.04.2015, z: https://docs.oracle.com/javase/tutorial/reflect
26. The Official YAML Website. (b.d.). Pobrano 12.04.2015, z: http://yaml.org
EduAkcja. Magazyn edukacji elektronicznej, nr 2 (10)/2015 , str. 53
Krzysztof Barteczko, Weryfikacja stylu programowania w rozwiązaniach zadań...
Coding Style Assessment in Programming Assignments
Summary
In the Polish-Japanese Academy of Information Technology the Programming Skills Development System was
created and implemented. Its main goal is to provide a model and tools for in-depth analysis and assessment of
students programming task’s solutions. System includes tools for automatic verification of programming style and
meeting the set of requirements in student’s solutions. The distinctive feature of the proposed tools is the ability
to set by educators their own rules of assessment in a variety of ways (appropriately to their knowledge, skills and
preferences). These tools are also characterized by simplicity of use, which is of great importance for their practical
application.
The article discusses the construction of verification tools and methods of defining tests for various problems
related to the quality of programming. Presented tools and procedures can be particularly useful in e-learning
environment.
Keywords: teaching programming, programming style, automatic assessment of programming style, testing scripts,
Java, Groovy, AST, XPath, PMD
EduAkcja. Magazyn edukacji elektronicznej, nr 2 (10)/2015 , str. 54

Podobne dokumenty