1 The Problem
We want a quiz engine — not a hard-coded quiz, but a system that loads questions from a JSON file, lets you pick a category, runs the quiz, and reports your score. It teaches building a reusable, data-driven application where the content lives in data, not code.
2 How to Think About It
Think about engine versus content, before any code:
3 The Build — explained part by part
Here is the complete quiz engine. Each part is explained below.
import json
def load_questions(filename):
with open(filename) as f:
return json.load(f)
def filter_by_category(questions, category):
return [q for q in questions if q["category"] == category]
def check_answer(question, given):
return given.strip().lower() == question["answer"].strip().lower()
def run_quiz(questions):
score = 0
for q in questions:
print("\n" + q["question"])
for i, option in enumerate(q["options"], 1):
print(f" {i}. {option}")
given = input("Your answer: ")
if check_answer(q, given):
print("Correct!")
score += 1
else:
print(f"Wrong. Answer: {q['answer']}")
return score
if __name__ == "__main__":
questions = load_questions("questions.json")
category = input("Category: ")
selected = filter_by_category(questions, category)
score = run_quiz(selected)
print(f"\nScore: {score} / {len(selected)}")
filter_by_category — keep only the questions in the chosen category, using a list comprehension.
check_answer — compare the player’s answer to the correct one, cleaned with
strip().lower() so spacing and capitals do not matter. Pulling this into its own function makes it testable.enumerate(q["options"], 1) — number the multiple-choice options starting at 1.
run_quiz — the engine loop: ask each question, check, score. It works for any set of questions, which is what makes it an engine rather than a single quiz.
strip().lower() on both sides.check_answer pure (no input/print) so it is testable.4 Test & Prove Each Part
We test the engine’s logic — filtering and answer-checking — without running an interactive quiz.
def filter_by_category(questions, category):
return [q for q in questions if q["category"] == category]
def check_answer(question, given):
return given.strip().lower() == question["answer"].strip().lower()
QUESTIONS = [
{"category": "math", "question": "2+2?", "answer": "4", "options": ["3", "4"]},
{"category": "geo", "question": "Capital of France?", "answer": "Paris", "options": ["Paris", "Rome"]},
]
def test_filter():
"""Filtering to 'math' returns one question."""
assert len(filter_by_category(QUESTIONS, "math")) == 1
def test_correct_answer():
"""A correct answer (any case) is accepted."""
assert check_answer(QUESTIONS[1], " paris ") is True
def test_wrong_answer():
"""A wrong answer is rejected."""
assert check_answer(QUESTIONS[0], "5") is False
Run with pytest -v. Because the engine’s logic is in small functions, we test filtering and checking without any typing. This is how you test an interactive app: isolate the logic from the input/output.
5 The Interface
What it expects
[{"category": "math", "question": "2+2?",
"options": ["3", "4"], "answer": "4"}]What it returns
2+2?
1. 3
2. 4
Your answer: 4
Correct!
Score: 1 / 16 Run It & Automate It
python3 quiz_engine.pyCreate a
questions.json file, pick a category, and take the quiz.Jenkins runs the tests automatically — each line explained below.
2+2?
1. 3
2. 4
Your answer: 4
Correct!
Score: 1 / 1KeyError loading questions.strip().lower() before comparing.// Jenkinsfile — automated tests on every change.
pipeline {
agent any
stages {
stage('Get the code') { steps { checkout scm } } // download the code
stage('Install') { steps { sh 'python3 -m venv venv && . venv/bin/activate && pip install pytest' } } // test tool
stage('Test') { steps { sh '. venv/bin/activate && pytest -v' } } // run every test
}
post {
success { echo 'All tests passed.' }
failure { echo 'A test failed — look above.' }
}
}
- Shuffle questions. Randomise order each run. (Teaches:
random.shuffle.) - Timed mode. Limit seconds per question. (Teaches: timing.)
- High scores. Save best scores per category. (Teaches: persistence.)