Cara Membuat Ujian Online Anti Contek dengan Google Apps Script: Kunci Aman, Acak Soal, dan Autosave
Membuat ujian online dengan Google Form memang cepat, tapi ada celah yang sering tidak disadari guru: kunci jawaban ikut terkirim ke browser (bisa diintip lewat inspect element), urutan soal sama untuk semua siswa, dan jawaban hilang begitu HP mati di tengah ujian.
Di tutorial ini kita membahas cara membuat ujian online yang lebih aman dengan Google Apps Script: kunci jawaban tetap di server, soal dan opsi diacak per siswa, jawaban tersimpan otomatis, dan pindah tab tercatat. Contohnya diambil dari aplikasi Ujian Online CBT yang bisa kamu download gratis di Papicode (link di akhir artikel).
1. Kirim soal tanpa kunci jawaban
Kesalahan paling umum adalah mengirim seluruh baris soal ke browser lalu menilai di JavaScript. Siapa pun bisa membuka DevTools dan melihat kuncinya. Solusinya: pilih hanya kolom yang dibutuhkan sebelum dikirim.
function soalUntukSiswa_(semua) {
return semua.map(function (q) {
return {
id: q.id,
tanya: q.tanya,
gambar: q.gambar,
opsi: ['A', 'B', 'C', 'D'].map(function (k) { return { k: k, teks: q.opsi[k] }; })
// q.kunci dan q.bobot sengaja TIDAK dikirim
};
});
}
Browser hanya mengirim balik huruf pilihan siswa. Penilaian terjadi di server.
2. Acak soal dengan seed supaya urutannya stabil
Kalau memakai Math.random(), urutan soal berubah setiap kali siswa memuat ulang halaman. Membingungkan, apalagi kalau siswa sudah menjawab sebagian. Pakai pengacak dengan seed dari NIS + token ujian, sehingga hasilnya acak antar siswa tapi selalu sama untuk siswa yang sama:
function seed_(teks) {
var h = 7;
for (var i = 0; i < teks.length; i++) h = (h * 31 + teks.charCodeAt(i)) % 2147483647;
return h;
}
function acak_(arr, seed) { // Fisher-Yates dengan generator angka sederhana
var a = arr.slice(), s = seed;
for (var i = a.length - 1; i > 0; i--) {
s = (Math.imul(s, 1103515245) + 12345) >>> 0;
var j = s % (i + 1), t = a[i]; a[i] = a[j]; a[j] = t;
}
return a;
}
var s = seed_(nis + token);
var urutSoal = acak_(semuaSoal, s);
var urutOpsi = acak_(['A', 'B', 'C', 'D'], s + nomor * 97); // opsi juga diacak per soal
Yang diacak hanya posisi tampil. Huruf asli (k) tetap dikirim bersama teks opsi, jadi saat dinilai server tidak perlu tahu urutan acaknya.
3. Autosave jawaban dengan CacheService
Menulis ke sheet setiap kali siswa memilih jawaban itu lambat dan rawan bentrok bila 40 siswa ujian bersamaan. Simpan sesi ujian di CacheService dulu, dan tulis ke sheet hanya sekali saat dikumpulkan:
function simpanJawaban(token, id, pilihan, ragu) {
var sesi = sesi_(token); // validasi token + ambil sesi dari cache
if (pilihan && 'ABCD'.indexOf(pilihan) < 0) throw new Error('Pilihan tidak valid.');
if (Date.now() > sesi.mulai + durasiMs()) throw new Error('Waktu ujian sudah habis.');
if (pilihan) sesi.jawaban[id] = pilihan; else delete sesi.jawaban[id];
if (ragu) sesi.ragu[id] = true; else delete sesi.ragu[id];
CacheService.getScriptCache().put('sesi_' + sesi.nis, JSON.stringify(sesi), 21600);
}
Waktu mulai disimpan di server, bukan di browser. Jadi siswa yang memuat ulang halaman tetap melihat sisa waktu yang benar, dan tidak bisa menambah waktu dengan mengubah jam HP.
4. Nilai di server dengan bobot dan LockService
Saat dikumpulkan, hitung nilai dari kunci di sheet, lalu tulis ke sheet Hasil di dalam kunci (lock) supaya satu NIS tidak tercatat dua kali:
function kumpulkan(token) {
var lock = LockService.getScriptLock();
lock.waitLock(20000);
try {
var sesi = sesi_(token);
if (bacaHasil_().some(function (h) { return h.nis === sesi.nis; })) throw new Error('Sudah dikumpulkan.');
var skor = 0, total = 0;
bacaSoal_().forEach(function (q) {
total += q.bobot;
if (sesi.jawaban[q.id] === q.kunci) skor += q.bobot;
});
var nilai = Math.round(skor / total * 1000) / 10; // 0 - 100, satu desimal
sheetHasil.appendRow([sesi.mulai, new Date(), sesi.nis, sesi.nama, sesi.kelas, nilai, JSON.stringify(sesi.)]);
.();
.().( + sesi.);
{ : nilai };
} {
lock.();
}
}
Dengan kolom Bobot, soal uraian singkat atau soal HOTS bisa diberi nilai 2 atau 3 tanpa mengubah kode.
5. Catat siswa yang pindah tab
Browser memberi tahu ketika halaman disembunyikan lewat event visibilitychange. Kirim ke server untuk dicatat, dan kumpulkan otomatis kalau sudah melewati batas:
document.addEventListener('visibilitychange', function () {
if (document.visibilityState !== 'hidden' || selesai) return;
google.script.run.withSuccessHandler(function (r) {
if (r.paksaSelesai) kirim(true); // langsung kumpulkan
else toast('Peringatan: kamu keluar dari halaman ujian (' + r.jumlah + 'x).');
}).catatPelanggaran(U.token);
});
Jumlah pelanggaran disimpan di sesi server dan ikut tercatat di sheet Hasil, jadi guru bisa melihatnya di dashboard. Ini bukan pengaman mutlak (siswa tetap bisa memakai HP lain), tapi cukup untuk mencegah kebiasaan membuka Google di tengah ujian.
6. Analisis butir soal
Karena jawaban lengkap tiap siswa disimpan sebagai JSON, guru bisa melihat soal mana yang terlalu mudah atau sulit:
var benar = hasil.filter(function (h) { return h.jawaban[q.id] === q.kunci; }).length;
var pct = Math.round(benar / hasil.length * 100);
var level = pct >= 70 ? 'Mudah' : pct >= 30 ? 'Sedang' : 'Sulit';
Hitung juga berapa siswa memilih A, B, C, dan D. Kalau sebagian besar memilih opsi yang sama tapi bukan kunci, kemungkinan soalnya ambigu atau kuncinya salah ketik.
Ringkasan
- Jangan kirim kunci jawaban ke browser; nilai dihitung di server.
- Acak soal dan opsi dengan seed dari NIS agar urutan stabil saat halaman dimuat ulang.
- Simpan jawaban di CacheService dan waktu mulai di server.
- Bungkus penyimpanan nilai dengan LockService agar satu NIS tidak tercatat dua kali.
- Catat pindah tab dengan
visibilitychangedan kumpulkan otomatis di batas tertentu.
Download source code lengkapnya
Semua teknik di atas dipakai di Ujian Online CBT (Google Apps Script): tampilan ujian ala kuis dengan 4 tombol berwarna, timer, ragu-ragu, hasil dan pembahasan, plus dashboard guru dengan sebaran nilai, tuntas KKM, peringkat, analisis butir soal, dan ekspor Excel. Gratis dan tanpa hosting.
Source code lengkapnya gratis
Lihat semua yang dibahas di artikel ini langsung berjalan.