Download mathly2/tests/test_exam.py from wzender/mathly: direct link, hf CLI and curl.
- Browser
- Download file 5.4 kB
-
https://huggingface.co/spaces/wzender/mathly/resolve/main/mathly2/tests/test_exam.py
- Command line
-
hf download hf://spaces/wzender/mathly/mathly2/tests/test_exam.py
-
curl -L -o test_exam.py https://huggingface.co/spaces/wzender/mathly/resolve/main/mathly2/tests/test_exam.py
5.4 kB
| """Drawing exercise tests and simulations (add-test-readiness 1.4).""" | |
| import random | |
| from collections import Counter | |
| import pytest | |
| from mathly2 import exam | |
| from mathly2.bank import load_bank | |
| def bank(): | |
| return load_bank() | |
| def lessons_and_skills(bank, count=2, per=4): | |
| """Lessons whose skills all have questions, enough skills in total: (lessons, skill ids).""" | |
| sizes = Counter(s["unit"] for s in bank.skills if bank.question_keys(s["id"])) | |
| lessons = [l for l, c in sizes.items() if c == per][:count] | |
| return lessons, [s["id"] for s in bank.skills if s["unit"] in lessons] | |
| def states(skills, learning=(), mastered=(), **kw): | |
| out = {i: "not_started" for i in skills} | |
| out.update({i: "learning" for i in learning}) | |
| out.update({i: "mastered" for i in mastered}) | |
| return out | |
| def per_skill(drawn): | |
| return Counter(q["skill_id"] for q in drawn["questions"]) | |
| def test_few_weak_skills_give_one_question_each_and_the_rest_comes_from_other_skills(bank): | |
| lessons, skills = lessons_and_skills(bank) # 8 skills | |
| weak = skills[:3] | |
| got = exam.draw(bank, lessons, states(skills, learning=weak, mastered=skills[3:]), 12, share=0.6, rng=random.Random(1)) | |
| assert len(got["questions"]) == 12 and got["weak"] == weak | |
| counts = per_skill(got) | |
| assert sum(counts[i] for i in weak) == 3 # 7 weak needed, only 3 weak skills: 3 weak questions | |
| assert sum(counts[i] for i in skills[3:]) == 9 # the other 9 from other covered skills | |
| def test_enough_weak_skills_give_60_percent_in_test_1_and_30_in_test_2(bank): | |
| lessons, skills = lessons_and_skills(bank, count=3) # 12 skills | |
| weak, others = skills[:9], skills[9:] | |
| st = states(skills, learning=weak, mastered=others) | |
| one = exam.draw(bank, lessons, st, 10, share=exam.EXERCISE_SHARE[1], rng=random.Random(2)) | |
| two = exam.draw(bank, lessons, st, 10, share=exam.EXERCISE_SHARE[2], rng=random.Random(3)) | |
| # the other skills are then drawn from round after round; the weak ones come first, one per skill | |
| assert sum(per_skill(one)[i] for i in weak) >= 6 and sum(per_skill(two)[i] for i in weak) >= 3 | |
| assert len(one["questions"]) == len(two["questions"]) == 10 | |
| def test_a_not_started_skill_is_never_drawn_and_is_listed_to_practise_first(bank): | |
| lessons, skills = lessons_and_skills(bank) | |
| st = states(skills, learning=skills[:2], mastered=skills[2:5]) # skills[5:] are not started | |
| got = exam.draw(bank, lessons, st, 12, share=0.6, rng=random.Random(4)) | |
| assert got["practice_first"] == skills[5:] | |
| assert not set(per_skill(got)) & set(skills[5:]) | |
| def test_include_unstarted_draws_the_not_started_skills_too(bank): | |
| lessons, skills = lessons_and_skills(bank) | |
| got = exam.draw(bank, lessons, states(skills), 12, share=0.6, include_unstarted=True, rng=random.Random(11)) | |
| assert len(got["questions"]) == 12 and got["practice_first"] == skills | |
| def test_a_skill_answered_wrong_in_an_earlier_exercise_test_counts_as_weak(bank): | |
| lessons, skills = lessons_and_skills(bank) | |
| st = states(skills, mastered=skills) # everything mastered: nothing weak by state | |
| got = exam.draw(bank, lessons, st, 12, share=0.6, weak_before={skills[0], skills[1]}, rng=random.Random(5)) | |
| assert set(got["weak"]) == {skills[0], skills[1]} | |
| assert per_skill(got)[skills[0]] >= 1 and per_skill(got)[skills[1]] >= 1 | |
| def test_a_retake_takes_new_questions_while_enough_remain(bank): | |
| lessons, skills = lessons_and_skills(bank) | |
| st = states(skills, learning=skills) | |
| used: Counter = Counter() | |
| first = exam.draw(bank, lessons, st, 12, share=0.6, used=used, rng=random.Random(6)) | |
| used.update(q["key"] for q in first["questions"]) | |
| second = exam.draw(bank, lessons, st, 12, share=0.6, used=used, rng=random.Random(7)) | |
| assert not {q["key"] for q in first["questions"]} & {q["key"] for q in second["questions"]} | |
| def test_the_simulation_spreads_one_question_per_skill_round_after_round(bank): | |
| lessons, skills = lessons_and_skills(bank) # 8 skills | |
| st = states(skills, learning=skills[:2], mastered=skills[2:]) | |
| got = exam.draw(bank, lessons, st, 12, share=0.0, rng=random.Random(8)) | |
| counts = per_skill(got) | |
| assert len(got["questions"]) == 12 and set(counts) == set(skills) and sorted(counts.values()) == [1, 1, 1, 1, 2, 2, 2, 2] | |
| assert len({q["key"] for q in got["questions"]}) == 12 | |
| def test_nothing_started_gives_no_questions_and_everything_is_listed(bank): | |
| lessons, skills = lessons_and_skills(bank) | |
| got = exam.draw(bank, lessons, states(skills), 12, share=0.6, rng=random.Random(9)) | |
| assert got["questions"] == [] and got["practice_first"] == skills and got["weak"] == [] | |
| def test_a_small_pool_gives_what_there_is_and_the_draw_is_repeatable(bank): | |
| lessons, skills = lessons_and_skills(bank, count=1) | |
| st = states(skills, learning=skills[:1]) | |
| few = exam.draw(bank, lessons, st, 40, share=0.6, rng=random.Random(10)) | |
| assert len(few["questions"]) == len(bank.question_keys(skills[0])) # one started skill: its own questions only | |
| a = exam.draw(bank, lessons, states(skills, learning=skills), 12, share=0.6, rng=random.Random(11)) | |
| b = exam.draw(bank, lessons, states(skills, learning=skills), 12, share=0.6, rng=random.Random(11)) | |
| assert a == b | |