V sistem se prijavimo z uporabniškim imenom in geslom, ki nam je bilo dodeljeno, oziroma preko svojega Google, Facebook ali Twitter računa.
Po uspešni prijavi se nam pokaže seznam predmetov, ki so v sistemu. Na vrhu seznama vidimo predmete, ki smo jih že dodali med priljubljene. To pomeni, da je naše ime na seznamu udeležencev (učencev) in da učitelj predmeta lahko pregleduje naše rešitve nalog. Predmeti, ki smo jih dodali na seznam priljubljenih, so označeni z . Predmet dodamo med priljubljene s klikom na
Če hočemo postati učitelj v predmetu, se moramo vanj najprej vpisati. Status učitelja v določenem predmetu nam bo dodelil eden od učiteljev tega predmeta oziroma administrator storitve Tomo. Predmeti, v katerih imamo status učitelja, so označeni z .
Predmet je sestavljen iz sklopov nalog. Na spodnji sliki vidimo predmet ‘Uvod v programiranje’ in seznam sklopov v njem.
Desno od seznama sklopov so našteta imena učiteljev v tem predmetu. Pod njimi je seznam učenecev - to so uporabniki, ki so predmet dodali med priljubljene. Poleg vsakega imena so (skriti) gumbi za spremembo statusa tega udeleženca - iz učitelja v učenca oziroma iz učenca v učitelja. Gumb se pokaže, ko miško postavimo na ime. Poleg učencev se pokaže , poleg učiteljev pa .
Številka levo od imena sklopa predstavlja delež nalog v tem sklopu, ki so jih učenci do sedaj že uspešno rešili. Na traku nad imenom sklopa je grafično prikazano, koliko nalog v tem sklopu so učenci uspešno rešili (zelena barva), neuspešno reševali (rumena barva) oziroma sploh še niso poskušali reševati (rdeča barva). Prikaz uspešnosti reševanja je bolj podrobno opisan v poglavju Pregled učenčevega dela.
Sklopi ponavadi zajemajo naloge podobne sorte. Na začetku sklopa je prostor za uvodno besedilo, v katerem so običajno razloženi osnovni ukazi, obravnavani v sklopu, temu pa sledijo posamezne naloge.
Na sliki 3 je prikazana vsebina sklopa ‘Pogojni stavek’:
Krogci poleg naslovov nalog na desni prikazujejo, kako uspešno so učenci reševali posamezno nalogo. Rdeča barva prikazuje tiste, ki naloge še niso začeli reševati, rumena barva tiste, ki so nalogo neuspešno rešili in zelena barva tiste, ki so nalogo uspešno rešili. Prikaz uspešnosti reševanja je bolj podrobno opisan v poglavju Pregled učenčevega dela.
S klikom na gumb Dodaj sklop odpremo obrazec, v katerega vpišemo lastnosti novega sklopa: naslov, uvodno besedilo, vidnost sklopa in vidnost rešitev. Gumb se nahaja na koncu seznama sklopov v predmetu.
Za urejanje sklopov imamo na voljo naslednja orodja (gumbi se nahajajo desno od imen sklopov na seznamu vseh sklopov v predmetu):
Gumb | Pomen |
---|---|
Odpre meni za urejanje lastnosti sklopa (naslov, uvodno besedilo, vidnost sklopa in vidnost rešitev). | |
Zbriše sklop in vse naloge v njem. | |
Sklop premaknemo za eno mesto višje oziroma nižje na seznamu sklopov v predmetu. | |
ali | Ikona označuje, da uporabniki s statusom učenca tega sklopa ne vidijo. Uporabniki s statusom učitelja bodo sklop vseeno lahko videli in urejali. označuje, da lahko sklop in vso njegovo vsebino vidijo tudi učenci. |
ali ali | Te ikone označujejo vidnost rešitev za vse naloge v sklopu:
|
Naloge so med seboj neodvisne, vsaka pa je sestavljena iz ene ali več podnalog, ki običajno gradijo ena na drugi in so postopoma težje.
Za urejanje nalog imamo na voljo naslednja orodja:
Gumb | Pomen |
---|---|
Nalogo premaknemo za eno mesto višje oziroma nižje na seznamu nalog v sklopu. | |
Odpre meni za urejanje lastnosti naloge (naslov in uvodno besedilo). | |
Zbriše nalogo in vse podnaloge v njej. | |
Odpre meni za kopiranje naloge v drug sklop. Izbiramo lahko med sklopi v predmetih, kjer imamo status učitelja. |
S klikom na gumb odpremo stran, ki prikazuje rešitve dane naloge. Na strani so prikazane naše rešitve in rešitve tistega, ki je nalogo sestavil - uradne rešitve. Primer vidimo na spodnji sliki:
S klikom na gumb Dodaj nalogo (nahaja se na dnu seznama nalog) odpremo obrazec, v katerega vpišemo naslov in uvodno besedilo nove naloge. Ko vsebino obrazca shranimo, se na strežniku naredi nova datoteka, ki vsebuje naslov in uvodno besedilo nove naloge (v obliki komentarjev) ter vso podporno programsko kodo, ki skrbi za komunikacijo s strežnikom. Mi moramo dodati samo še besedilo podnalog ter njihove rešitve.
Besedilo in rešitve podnalog vpisujemo neposredno v datoteko. Zato datoteko najprej prenesemo s strežnika in sicer s klikom na gumb pri čemer izberemo možnost Datoteka za urejanje.
S klikom na možnost Datoteka za reševanje prenesemo datoteko, pripravljeno za reševanje. To pomeni, da datoteka ne vsebuje naših rešitev in testov, ampak samo besedilo naloge. Tako datoteko s strežnika prenesejo učenci in vanjo vpisujejo svoje rešitve.
Datoteka, ki jo prenesemo s klikom na Datoteka za urejanje, pa vsebuje tudi naše rešitve in teste. Pri novo ustvarjeni nalogi med obema tipoma datotek še ni razlik, ko pa bomo že dodali nekaj podnalog in testov in bomo kasneje želeli nalogo še dopolniti, moramo prenesti datoteko za urejanje.
Če želimo prenesti datoteke vseh nalog v sklopu hkrati, kliknemo na gumb , ki se nahaja nad prvo nalogo v sklopu.
Na sliki 6 vidimo naslov in uvodno besedilo naloge ‘Ploščine likov’. Nad in pod besedilom je programska koda, ki skrbi za komunikacijo s strežnikom. Te kode ne spreminjamo.
Za oblikovanje besedila uporabimo jezik Markdown. Osnovne ukaze najdemo
tukaj.
Brskalnik bo na podlagi uporabljenih ukazov prikazal ustrezno oblikovano
besedilo. **Krog**
bo na primer videti kot Krog.
Za pisanje matematičnih formul uporabimo običajne LaTeX ukaze, ki jih bo brskalnik prikazal s pomočjo knjižnice MathJax. Več o knjižnici MathJax piše tukaj.
Besedilo podnaloge napišemo pod #=========
, kot je prikazano na sliki 8. Za
navodili napišemo uradno rešitev, za uradno rešitvijo pa teste, ki jih mora
oddana rešitev prestati. Rešitev od testov ločimo z ukazom
Check.part()
.
Ko napišemo vse, kar smo želeli, datoteko poženemo. S tem vsebino shranimo na strežnik in hkrati preverimo, ali smo vse pravilno zapisali. Ob zagonu se bo naša uradna rešitev preverila na napisanih testih, ki nam bodo javili morebitne napake.
Ko besedilo podnaloge prvič shranimo na strežnik, se bo podnaloga shranila v
bazo in dobila svoj ID. Ta ID bo prikazan na koncu niza #========
, pod katerim
smo zapisali besedilo podnaloge. Tega ID-ja ne spreminjajmo in ne brišimo.
Za testiranje rešitev imamo na voljo funkcije, opisane v nadaljevanju.
Check.equal
S Check.equal(''izraz'', pricakovani_rezultat)
, preverimo, ali
je rezultat danega izraza enak pričakovanemu. Izraz je ponavadi kar klic
funkcije, ki jo preverjamo, ni pa nujno - lahko je poljubno število, niz, izraz
... v jeziku Python. Izraz mora biti podan kot niz, torej zapisan med
narekovaji. Pričakovani rezultat je lahko poljubno število, niz, izraz,… v
jeziku Python.
Oglejmo si spodnji primer:
# ====================================================
# Napiši funkcijo 'razdalja(x1, y1, x2, y2)', ki vrne
# razdaljo med točkama (x1, y1) in (x2, y2).
# ====================================================
def razdalja(x1, y1, x2, y2):
return ((x1 - x2) ** 2 + (y1 - y2) ** 2) ** (1/2)
Check.part()
Check.equal('razdalja(0, 0, 3, 4)', 5)
Check.equal('razdalja(1, 2, 3, 4)**2', 8)
Zgoraj vidimo primer, ko prvi argument funkcije Check.equal
ni samo klic funkcije, ki jo testiramo.
Če vrednost izraza ni enaka pričakovanemu rezultatu, se učencu na zaslon izpiše, za katere testne podatke je njegova funkcija vrnila napačen rezultat in kakšen je pričakovani rezultat. Na primer, če bi učenec pri računanju razdalje pozabil na koren, bi bila povratna informacija take oblike:
- Izraz razdalja(0, 0, 3, 4) vrne 25 namesto 5.
- Izraz razdalja(1, 2, 3, 4)**2 vrne 64 namesto 8.
Check.secret
Ker se testiranje rešitve izvrši na učenčevem računalniku, so vsi testi vsebovani v datoteki za reševanje. Učenci lahko teste najdejo in vidijo pričakovane rezultate. Nato napišejo rešitev, ki uspešno prestane vse teste in bo zato sprejeta, vendar ni pravilna:
def razdalja(x1, y1, x2, y2):
if (x1, y1, x2, y2) == (0, 0, 3, 4):
return 5
if (x1, y1, x2, y2) == (1, 2, 3, 4):
return math.sqrt(8)
Učenci bi teste lahko tudi pobrisali in s tem dosegli, da bo rešitev
sprejeta, saj testi ne bi javili napak. Take goljufije preprečimo z uporabo
funkcije Check.secret(ime_funkcije(arg1, arg2,…))
. Funkcija določi
vrednost izraza ime_funkcije(arg1, arg2,…)
in jo pošlje na
strežnik. Strežnik primerja dobljeno vrednost z rezultatom, ki ga na istih
podatkih vrne učiteljeva rešitev (ti rezultati se samodejno izračunajo, ko
učitelj shrani svojo rešitev na strežnik). Če bi učenec teste zbrisal, bi
strežnik zaznal, da ni dobil vrednosti za primerjavo in rešitve ne bi
sprejel.
Učenci v datoteki sicer vidijo, s na katerih testnih podatkih se bo rešitev
preverjala, vendar ne vidijo pričakovanega rezultata. Tudi v povratni
informaciji, ki se pokaže po oddaji neustrezne rešitve, ne bo zapisan. Povratna
informacija bo oblike Rešitev podnaloge 3 ni sprejeta.
Goljufanje še dodatno otežimo, če rešitev preverjamo na stotinah na videz naključnih podatkov:
for i in range(100):
Check.secret(razdalja(i - 50, i * 17, i + 13, i/2)
Koda v zgoraj zapisanem primeru bo izračunala razdaljo med pari točk (-50, 0) & (13, 0), (-49, 17) & (14, 0), (-48, 34) & (15, 1), …
Funkcija Check.secret
je zato najbolj primerna za preverjanje
izpitnih nalog ali ko želimo zagotoviti, da učenci pri reševanju ne bodo ubirali
bližnjic.
Check.run
Ko preverjamo programe, ki spreminjajo vrednost lokalnih spremenljivk (na
primer seznamov ali slovarjev), moramo poleg rezultata programa preveriti tudi
vrednosti spremenljivk. V ta namen uporabimo funkcijo Check.run(izrazi,
pricakovano_stanje)
. izrazi so izrazi v jeziku Python, zapisani kot niz
(torej med narekovaji). pricakovano_stanje
je slovar vrednosti, ki naj bi jih
spremenljivke imele po izračunu podanih izrazov. Na primer:
# =========================================================
# Napiši funkcijo ’spremeni_predznak(stevila)’, ki spremeni
# predznak vsem elementom seznama ‘stevila’.
# =====================================================
def spremeni_predznak(stevila):
for i in range(len(stevila)):
stevila[i] = -stevila[i]
Check.part()
Check.run([
'a = [1, 3, -5, 2, -6]',
'spremeni_predznak(a)',
'b = [1, -5, 2, 5]',
'spremeni_predznak(b)',
'spremeni_predznak(b)'
], {'a': [-1, -3, 5, -2, 6], 'b': [1, -5, 2, 5]})
Če stanje spremenljivk po izvedbi izrazov ni enako pričakovanemu, bo
reševalec prejel povratno informacijo o tem, za katere izraze njegova rešitev ne
vrne pričakovanih rezultatov. Na primer, če bi zgornjo nalogo reševalec rešil
tako, da se seznam a
ne bi spremenil, bi bila povratna informacija oblike:
- Po izvedbi izrazov
>>> a = [1, 3, -5, 2, -6]
>>> spremeni_predznak(a)
>>> b = [1, -5, 2, 5]
>>> spremeni_predznak(b)
>>> spremeni_predznak(b)
je vrednost spremenljivke a enaka [1, 3, -5, 2, -6] namesto [-1, -3, 5, -2, 6].
Check.out_file
Za preverjanje rešitev, ki vračajo datoteke, uporabljamo
Check.out_file(ime_datoteke, pricakovana_vsebina)
. Funkcija
preveri, ali se vsebina datoteke ime_datoteke
ujema s pricakovana_vsebina
.
pricakovana_vsebina
je seznam vrstic, ki naj bi jih datoteka vsebovala.
Če na primer naloga zahteva, da učenec napiše funkcijo
napisi_abecedo(n, abeceda.txt)
, ki v datoteko
abeceda.txt
zapiše prvih n
črk abecede, bi test
izgledal takole:
napisi_abecedo(3, 'abeceda.txt')
Check.out_file('abeceda.txt', [
'a', 'b', 'c',
])
Če se vsebina datoteke razlikuje od pričakovane, bodo v povratni informaciji tiste vrstice označene z *
:
- Izhodna datoteka abeceda.txt
je enaka namesto:
e *a
d * b
c | c
Check.in_file
Za testiranje programov, ki berejo iz podane datoteke, uporabljamo
Check.in_file(ime_datoteke, vsebina)
. Funkcija ustvari datoteko z
danim imenom in vsebino. Delovanje si oglejmo na primeru Napiši funkcijo
Testi bi bili na primer takšni:prestej_vrstice(ime_datoteke)
, ki vrne število vrstic v podani
datoteki.
with Check.in_file('test_vhodna_1.txt', ['a', 'b', 'c',]):
Check.equal('prestej_vrstice("test_vhodna_1.txt")', 3)
with Check.in_file('test_vhodna2.txt', []):
Check.equal('prestej_vrstice("test_vhodna2.txt")', 0)
Kot lahko vidimo, funkcijo Check.equal
kličemo v kontekstu, za
katerega poskrbi funkcija Check.in_file
. Če je potrebno, lahko v
istem kontekstu kličemo več testnih funkcij (ne samo ene, kot v zgornjem
primeru). Funkcijo Check.in_file
lahko kličemo večkrat in na ta
način rešitev preverimo pri različnih vhodnih datotekah.
Check.in_file
poskrbi, da povratna informacija vsebuje tudi ime
in vsebino datoteke, pri kateri oddana rešitev ne vrne pričakovanih
rezultatov:
- Pri vhodni datoteki test_vhodna.txt z vsebino
a
b
c
je prišlo do naslednjih napak:
- Izraz prestej_vrstice("test_vhodna1.txt") vrne 4 namesto 3.
Testi so pisani v jeziku Python, zato lahko pri pisanju uporabimo vse funkcionalnosti, ki jih ta jezik ponuja: pogojni stavki, značke, knjižnice ...
Osnovni ukaz, ki ga pri teh testih uporabljamo, je Check.error(message)
, ki vrne napako in njen opis.
if f(100) > 1000:
Check.error("Vrednost f(100) je prevelika!")
Na voljo je tudi razširjena oblika ukaza: Check.error(message, arg1,
arg2, ...)
. V niz message
lahko vključimo spremenljivke
arg1
, arg2
itd. in sicer tako, da v niz med
{
in }
napišemo zaporedno številko mesta, na katerem
je spremenljivka pri klicu funkcije (šteti začnemo z 0). Na primer, v spodnjem
primeru med zavite oklepaje zapišemo številko 0, ker je argument i
na prvem mestu.
for x in [100, 200, 300, 400]:
if f(i) > 10 * i:
Check.error("Vrednost f({0}) je prevelika!", i)
Če želimo preverjati kodo učenčeve rešitve, to storimo preko
Check.current_part['solution']
. Preverimo lahko na primer, ali je
učenec pri reševanju uporabil zanke namesto rekurzije, to je, ali koda vsebuje
besedi for
ali while
.
Za bolj natančno analizo kode si pomagamo s knjižnico ast.
Ukazi Check.equal
, Check.run
in
Check.out_file
vrnejo True
ali False
.
Zato z njimi lahko preverimo, ali je sploh smiselno, da oddano rešitev še
dodatno testiramo. Če rešitev ne prestane prvega testa, potem je očitno ne
moremo sprejeti, zato nadaljnji testi niso potrebni.
Če pa želimo učencem pokazati, za katere nabore testnih podatkov njihov program ne vrne pričakovanih rezultatov, lahko pripravimo poljubno mnogo testov.
Tudi mi potrebujemo povratne informacije o tem, kako hitro in uspešno so učenci reševali določeno nalogo ali kako napreduje posamezen učenec.
Tomo prikaže statistiko reševanja za posamezno nalogo in statistiko reševanja za posameznega učenca.
Splošna statistika za posamezno nalogo je prikazana s tortnimi grafikoni. Grafikoni se nahajajo poleg seznama vseh nalog v sklopu, kot prikazuje spodnja slika. Vsaka podnaloga dane naloge ima svoj grafikon. Rdeča barva prikazuje delež učencev, ki podnaloge še niso začeli reševati. Rumena barva prikazuje tiste, ki so nalogo rešili narobe in zelena barva tiste, katerih rešitev je sprejeta.
Če si želimo uspešnost reševanja ogledati bolj podrobno, kliknemo na ime naloge. Odpre se stran, kjer je prikazana uspešnost reševanja posameznih študentov pri posameznih podnalogah. Rdeče obarvan krogec pomeni, da učenec podnaloge še ni poskušal reševati. Rumen krogec pomeni, da je učenec podnalogo rešil napačno, zelen krogec pa, da je učenčeva rešitev sprejeta.
Če kliknemo na obarvani krogec, se nam odpre stran z rešitvami tega učenca. Poleg učenčevih rešitev vidimo še uradne rešitve podnalog.
Na prvi strani predmeta je objavljen seznam vseh učencev v predmetu. S klikom na ime učenca odpremo stran, ki prikazuje, kako uspešno je ta učenec reševal naloge v celotnem predmetu. Tudi tukaj rdeča barva pomeni, da učenec podnaloge še ni poskušal reševati. Rumena barva pomeni, da njegova rešitev podnaloge ni bila sprejeta in zelena barva pomeni, da je podnalogo uspešno rešil.
Če kliknemo na obarvani krogec, se nam odpre stran z rešitvami tega učenca (glej sliko 10).