CSC148-Winter-2023-A1 / tests.py
tests.py
Raw
import course
import survey
import criterion
import grouper
import pytest
import unittest
from course import Student
from course import Course
from survey import Question, YesNoQuestion, MultipleChoiceQuestion, \
    NumericQuestion, CheckboxQuestion, Answer
from criterion import InvalidAnswerError, Criterion, HomogeneousCriterion,\
    HeterogeneousCriterion, LonelyMemberCriterion
from grouper import Group, Grouping
from survey import Survey


@pytest.fixture
def empty_course() -> course.Course:
    return course.Course('csc148')


@pytest.fixture
def students() -> list[course.Student]:
    return [course.Student(1, 'Zoro'),
            course.Student(2, 'Aaron'),
            course.Student(3, 'Gertrude'),
            course.Student(4, 'Yvette')]


@pytest.fixture
def alpha_grouping(students_with_answers) -> grouper.Grouping:
    grouping = grouper.Grouping()
    grouping.add_group(grouper.Group([students_with_answers[0],
                                      students_with_answers[3]]))
    grouping.add_group(grouper.Group([students_with_answers[1],
                                      students_with_answers[2]]))
    return grouping

@pytest.fixture
def other_alpha_grouping(students_with_answers) -> grouper.Grouping:
    grouping = grouper.Grouping()
    grouping.add_group(grouper.Group([students_with_answers[1],
                                      students_with_answers[3],
                                      students_with_answers[2]]))
    grouping.add_group(grouper.Group([students_with_answers[0]]))
    return grouping


@pytest.fixture
def greedy_grouping(students_with_answers) -> grouper.Grouping:
    grouping = grouper.Grouping()
    grouping.add_group(grouper.Group([students_with_answers[1],
                                      students_with_answers[3]]))
    grouping.add_group(grouper.Group([students_with_answers[0],
                                      students_with_answers[2]]))
    return grouping

@pytest.fixture
def other_greedy_grouping(students_with_answers) -> grouper.Grouping:
    grouping = grouper.Grouping()
    grouping.add_group(grouper.Group([students_with_answers[1],
                                      ]))
    grouping.add_group(grouper.Group([students_with_answers[3],
                                      students_with_answers[0],
                                      students_with_answers[2]]))
    return grouping



@pytest.fixture
def sa_grouping(students_with_answers) -> grouper.Grouping:
    grouping = grouper.Grouping()
    grouping.add_group(grouper.Group([students_with_answers[2],
                                      students_with_answers[0]]))
    grouping.add_group(grouper.Group([students_with_answers[3],
                                      students_with_answers[1]]))
    return grouping


@pytest.fixture
def questions() -> list[survey.Question]:
    return [survey.MultipleChoiceQuestion(1, 'why?', ['a', 'b']),
            survey.NumericQuestion(2, 'what?', -2, 4),
            survey.YesNoQuestion(3, 'really?'),
            survey.CheckboxQuestion(4, 'how?', ['a', 'b', 'c'])]


@pytest.fixture
def criteria(answers) -> list[criterion.Criterion]:
    return [criterion.HomogeneousCriterion(),
            criterion.HeterogeneousCriterion(),
            criterion.LonelyMemberCriterion(),
            criterion.HomogeneousCriterion()]


@pytest.fixture()
def weights() -> list[int]:
    return [2, 5, 7, 4]


@pytest.fixture
def answers() -> list[list[survey.Answer]]:
    return [[survey.Answer('a'), survey.Answer('b'),
             survey.Answer('a'), survey.Answer('b')],
            [survey.Answer(0), survey.Answer(4),
             survey.Answer(-1), survey.Answer(1)],
            [survey.Answer(True), survey.Answer(False),
             survey.Answer(True), survey.Answer(True)],
            [survey.Answer(['a', 'b']), survey.Answer(['a', 'b']),
             survey.Answer(['a']), survey.Answer(['b'])]]

@pytest.fixture
def invalid_answers() -> list[list[survey.Answer]]:
    return [[survey.Answer('a'), survey.Answer('b'),
             survey.Answer('a'), survey.Answer('b')],
            [survey.Answer(0), survey.Answer(4),
             survey.Answer(-1), survey.Answer(1)],
            [survey.Answer(True), survey.Answer(1),
             survey.Answer(True), survey.Answer(True)],
            [survey.Answer(['a', 'b']), survey.Answer(['a', 'b']),
             survey.Answer(['a']), survey.Answer(['b'])]]


@pytest.fixture
def students_with_answers(students, questions, answers) -> list[course.Student]:
    for i, student in enumerate(students):
        for j, question in enumerate(questions):
            student.set_answer(question, answers[j][i])
    return students

@pytest.fixture
def students_with_invalid_answers(students, questions, invalid_answers) -> \
        list[course.Student]:
    for i, student in enumerate(students):
        for j, question in enumerate(questions):
            student.set_answer(question, invalid_answers[j][i])
    return students


@pytest.fixture
def course_with_students(empty_course, students) -> course.Course:
    empty_course.enroll_students(students)
    return empty_course


@pytest.fixture
def course_with_students_with_answers(empty_course,
                                      students_with_answers) -> course.Course:
    empty_course.enroll_students(students_with_answers)
    return empty_course


@pytest.fixture
def survey_(questions, criteria, weights) -> survey.Survey:
    s = survey.Survey(questions)
    for i, question in enumerate(questions):
        s.set_weight(weights[i], question)
        s.set_criterion(criteria[i], question)
    return s


@pytest.fixture
def group(students) -> grouper.Group:
    return grouper.Group(students)


def get_member_ids(grouping: grouper.Grouping) -> set[frozenset[int]]:
    member_ids = set()
    for group in grouping.get_groups():
        ids = []
        for member in group.get_members():
            ids.append(member.id)
        member_ids.add(frozenset(ids))
    return member_ids


def compare_groupings(grouping1: grouper.Grouping,
                      grouping2: grouper.Grouping) -> None:
    assert get_member_ids(grouping1) == get_member_ids(grouping2)


# You may need to import pytest in order to run your tests.
# You are free to import hypothesis and use hypothesis for testing.
# This file will not be graded for style with PythonTA

###############################################################################
# Task 2 Test cases
###############################################################################
class TestStudentClass:

    def test_initializer(self) -> None:
        stu1 = Student(1, 'a')
        assert stu1.id == 1
        assert stu1.name == 'a'

    def test_str(self) -> None:
        stu1 = Student(1, 'a')
        assert str(stu1) == 'a'

    def test_has_answer_valid(self) -> None:
        stu1 = Student(1, 'a')
        qn = NumericQuestion(1, 'what?', 1, 10)
        ans = Answer(4)
        stu1.set_answer(qn, ans)
        assert stu1.has_answer(qn) is True

    def test_has_answer_invalid(self) -> None:
        stu1 = Student(1, 'a')
        qn = MultipleChoiceQuestion(1, 'what?', ['a', 'b', 'c'])
        ans = Answer(4)
        stu1.set_answer(qn, ans)
        assert stu1.has_answer(qn) is False

    def test_set_answer_existing_ans(self) -> None:
        stu1 = Student(1, 'a')
        qn = NumericQuestion(1, 'what?', 1, 10)
        ans1 = Answer(4)
        ans2 = Answer(5)
        stu1.set_answer(qn, ans1)
        stu1.set_answer(qn, ans2)
        assert stu1.get_answer(qn) == ans2

    def test_get_answer_no_qn(self) -> None:
        stu1 = Student(1, 'a')
        ans1 = Answer(4)
        qn1 = NumericQuestion(1, 'what?', 1, 10)
        qn2 = NumericQuestion(2, 'where?', 1, 10)
        stu1.set_answer(qn1, ans1)
        assert stu1.get_answer(qn2) is None


###############################################################################
# Task 3 Test cases
###############################################################################
class TestCourseClass:
    def test_initializer(self) -> None:
        test_course = Course('ABC')
        assert test_course.name == 'ABC'
        assert test_course.students == []

    def test_enroll_students_valid(self) -> None:
        test_course = Course('ABC')
        stu1 = Student(1, 'a')
        stu2 = Student(2, 'b')
        test_course.enroll_students([stu1, stu2])
        assert test_course.students == [stu1, stu2]

    def test_enroll_students_duplicates(self) -> None:
        test_course = Course('ABC')
        stu1 = Student(1, 'a')
        stu2 = Student(1, 'b')
        test_course.enroll_students([stu1, stu2])
        assert test_course.students == []

    def test_enroll_students_nameless(self) -> None:
        test_course = Course('ABC')
        stu1 = Student(1, 'a')
        stu2 = Student(2, '')
        test_course.enroll_students([stu1, stu2])
        assert test_course.students == []

    def test_all_answered_valid(self) -> None:
        test_course = Course('ABC')
        qn1 = NumericQuestion(1, 'what?', 1, 10)
        qn2 = NumericQuestion(2, 'where?', 1, 10)
        test_survey = Survey([qn1, qn2])
        ans1 = Answer(4)
        ans2 = Answer(5)
        stu1 = Student(1, 'a')
        stu1.set_answer(qn1, ans1)
        stu1.set_answer(qn2, ans2)
        stu2 = Student(2, 'b')
        stu2.set_answer(qn1, ans1)
        stu2.set_answer(qn2, ans2)
        assert test_course.all_answered(test_survey) is True

    def test_all_answered_invalid(self) -> None:
        test_course = Course('ABC')
        qn1 = NumericQuestion(1, 'what?', 1, 10)
        qn2 = NumericQuestion(2, 'where?', 1, 10)
        test_survey = Survey([qn1, qn2])
        ans1 = Answer(11)
        ans2 = Answer(5)
        stu1 = Student(1, 'a')
        stu1.set_answer(qn1, ans1)
        stu1.set_answer(qn2, ans2)
        stu2 = Student(2, 'b')
        stu2.set_answer(qn2, ans2)
        test_course.enroll_students([stu1, stu2])
        assert test_course.all_answered(test_survey) is False

    def test_get_students_order(self) -> None:
        test_course = Course('ABC')
        stu1 = Student(3, 'a')
        stu2 = Student(1, 'b')
        stu3 = Student(2, 'c')
        test_course.enroll_students([stu1, stu2, stu3])
        assert test_course.get_students() == (stu2, stu3, stu1)


###############################################################################
# Task 4 Test cases
###############################################################################
class TestQuestion:

    def test_question_main_class(self) -> None:
        q = Question(1, 'What?')
        assert q.id == 1
        assert q.text == 'What?'

    def test_mcq_initializer(self) -> None:
        mcq = MultipleChoiceQuestion(2, "Choose.", ['a', 'b', 'c', 'd'])
        assert mcq.id == 2
        assert mcq.text == "Choose."

    def test_mcq_str(self) -> None:
        mcq = MultipleChoiceQuestion(2, "Choose.", ['a', 'b', 'c', 'd'])
        expected = str(mcq)
        s = 'Choose.\n' \
            '- a\n' \
            '- b\n' \
            '- c\n' \
            '- d'
        assert expected == s

    def test_mcq_validate_answer_valid(self) -> None:
        mcq = MultipleChoiceQuestion(2, "Choose.", ['a', 'b', 'c', 'd'])
        answer = Answer('a')
        assert mcq.validate_answer(answer) is True

    def test_mcq_validate_answer_invalid(self) -> None:
        mcq = MultipleChoiceQuestion(2, "Choose.", ['a', 'b', 'c', 'd'])
        answer = Answer('e')
        assert mcq.validate_answer(answer) is False

    def test_numeric_initializer(self) -> None:
        num = NumericQuestion(2, "Enter a number?", 1, 10)
        assert num.id == 2
        assert num.text == "Enter a number?"

    def test_numeric_str(self) -> None:
        num = NumericQuestion(2, "Enter a number?", 1, 10)
        s = str(num)
        expected = "Enter a number?"
        assert expected == s

    def test_numeric_validate_answer_valid(self) -> None:
        num = NumericQuestion(2, "Enter a number?", 1, 10)
        answer1 = Answer(5)
        answer2 = Answer(10)
        answer3 = Answer(1)
        assert num.validate_answer(answer1) is True
        assert num.validate_answer(answer2) is True
        assert num.validate_answer(answer3) is True

    def test_numeric_validate_answer_invalid(self) -> None:
        num = NumericQuestion(2, "Enter a number?", 1, 10)
        answer1 = Answer(11)
        answer2 = Answer(-1)
        answer3 = Answer(0)
        assert num.validate_answer(answer1) is False
        assert num.validate_answer(answer2) is False
        assert num.validate_answer(answer3) is False

    def test_numeric_get_similarity_minimum(self) -> None:
        num = NumericQuestion(2, "Enter a number?", 1, 10)
        answer1 = Answer(1)
        answer2 = Answer(10)
        assert num.get_similarity(answer1, answer2) == 0.0

    def test_numeric_get_similarity_maximum(self) -> None:
        num = NumericQuestion(2, "Enter a number?", 1, 10)
        answer1 = Answer(10)
        answer2 = Answer(10)
        assert num.get_similarity(answer1, answer2) == 1.0

    def test_numeric_get_similarity_random(self) -> None:
        num = NumericQuestion(2, "Enter a number?", 1, 10)
        answer1 = Answer(5)
        answer2 = Answer(3)
        assert num.get_similarity(answer1, answer2) == 1.0 - 2 / 9

    def test_yn_initializer(self) -> None:
        yn = YesNoQuestion(1, "Yes or No?")

    def test_yn_str(self) -> None:
        yn = YesNoQuestion(1, "Yes or No?")
        assert str(yn) == 'Yes or No?\n' \
                          '- Yes\n' \
                          '- No'

    def test_yn_validate_valid(self) -> None:
        yn = YesNoQuestion(1, "Yes or No?")
        ans1 = Answer(True)
        ans2 = Answer(False)
        assert yn.validate_answer(ans1) is True
        assert yn.validate_answer(ans2) is True

    def test_yn_validate_invalid(self) -> None:
        yn = YesNoQuestion(1, "Yes or No?")
        ans1 = Answer(1)
        ans2 = Answer('Yes')
        assert yn.validate_answer(ans1) is False
        assert yn.validate_answer(ans2) is False

    def test_yn_get_similarity_same(self) -> None:
        yn = YesNoQuestion(1, "Yes or No?")
        ans1 = Answer(True)
        ans2 = Answer(True)
        assert yn.get_similarity(ans1, ans2) == 1.0

    def test_yn_get_similarity_different(self) -> None:
        yn = YesNoQuestion(1, "Yes or No?")
        ans1 = Answer(False)
        ans2 = Answer(True)
        assert yn.get_similarity(ans1, ans2) == 0.0


    def test_checkbox_validate_answer(self) -> None:
        checkbox = CheckboxQuestion(1, "Choose any.", ['a', 'b', 'c', 'd'])
        answer1 = Answer(['a', 'b', 'c'])
        answer2 = Answer([])
        answer3 = Answer(['a', 'a', 'a'])
        assert checkbox.validate_answer(answer1) is True
        assert checkbox.validate_answer(answer2) is False
        assert checkbox.validate_answer(answer3) is False


    def test_checkbox_similarity_random(self) -> None:
        checkbox = CheckboxQuestion(1, "Choose any.", ['a', 'b', 'c', 'd'])
        answer1 = Answer(['a', 'b', 'c'])
        answer2 = Answer(['c', 'b', 'd'])
        assert checkbox.get_similarity(answer1, answer2) == 0.5

    def test_checkbox_similarity_different(self) -> None:
        checkbox = CheckboxQuestion(1, "Choose any.", ['a', 'b', 'c', 'd',
                                                       'e', 'f'])
        answer1 = Answer(['a', 'b', 'c'])
        answer2 = Answer(['d', 'e', 'f'])
        assert checkbox.get_similarity(answer1, answer2) == 0.0

    def test_checkbox_similarity_same(self) -> None:
        checkbox = CheckboxQuestion(1, "Choose any.", ['a', 'b', 'c', 'd'])
        answer1 = Answer(['a', 'b', 'c'])
        answer2 = Answer(['a', 'b', 'c'])
        assert checkbox.get_similarity(answer1, answer2) == 1.0





###############################################################################
# Task 5 Test cases
###############################################################################
class TestAnswer:
    def test_answer_is_valid(self) -> None:
        ans = Answer('a')
        qn = CheckboxQuestion(1, 'Choose.', ['a', 'b', 'c'])
        assert ans.is_valid(qn) is True

    def test_answer_is_invalid(self) -> None:
        ans = Answer('d')
        qn = CheckboxQuestion(1, 'Choose.', ['a', 'b', 'c'])
        assert ans.is_valid(qn) is False


###############################################################################
# Task 6 Test cases
###############################################################################
class TestCriterion:
    def test_homogenous_invalid(self, criteria, answers) -> None:
        q = MultipleChoiceQuestion(1, "Choose.", ['a', 'b', 'c'])
        hom_criterion = criteria[0]
        with pytest.raises(InvalidAnswerError):
            hom_criterion.score_answers(q, answers[-1])

    def test_homogenous_none(self, criteria, answers) -> None:
        q = MultipleChoiceQuestion(1, "Choose.", ['a', 'b', 'c'])
        hom_criterion = criteria[0]
        with pytest.raises(InvalidAnswerError):
            hom_criterion.score_answers(q, answers[-1])


###############################################################################
# Task 7 Test cases
###############################################################################
class TestGroup:

    def test_group_initializer(self) -> None:
        stu1 = Student(1, 'a')
        stu2 = Student(2, 'b')
        stu3 = Student(3, 'c')
        grp = Group([stu1, stu2, stu3])
        members = grp.get_members()
        assert members[0].name == 'a'
        assert members[1].name == 'b'
        assert members[2].name == 'c'

    def test_group_str(self) -> None:
        stu1 = Student(1, 'a')
        stu2 = Student(2, 'b')
        stu3 = Student(3, 'c')
        stu4 = Student(4, 'd')
        grp = Group([stu1, stu2, stu3, stu4])
        assert str(grp) == 'a, b, c, d'

    def test_group_duplicate_students(self) -> None:
        stu1 = Student(1, 'a')
        stu2 = Student(2, 'b')
        stu3 = Student(3, 'c')
        grp = Group([stu1, stu2, stu3, stu3])
        assert str(grp) == 'a, b, c'



###############################################################################
# Task 8 Test cases
###############################################################################

class TestGrouping:
    def test_grouping_str(self) -> None:
        grouping = Grouping()
        stu1 = Student(1, 'a')
        stu2 = Student(2, 'b')
        stu3 = Student(3, 'c')
        stu4 = Student(4, 'd')
        stu5 = Student(5, 'e')
        stu6 = Student(6, 'f')
        grp1 = Group([stu1, stu2, stu3])
        grp2 = Group([stu4, stu5, stu6])
        grouping.add_group(grp1)
        grouping.add_group(grp2)
        assert str(grouping) == 'Groups\n' \
                                'a, b, c\n' \
                                'd, e, f'



# survey.MultipleChoiceQuestion(1, 'why?', ['a', 'b']),
# survey.NumericQuestion(2, 'what?', -2, 4),
# survey.YesNoQuestion(3, 'really?'),
# survey.CheckboxQuestion(4, 'how?', ['a', 'b', 'c'])]

###############################################################################
# Task 9 Test cases
###############################################################################
class TestSurvey:

    def test_survey_len(self) -> None:
        qn1 = Question(1, 'What?')
        qn2 = Question(2, 'Who?')
        qn3 = Question(3, 'Where?')
        test_survey = Survey([qn1, qn2, qn3])
        assert len(test_survey) == 3

    def test_survey_contains(self) -> None:
        qn1 = Question(1, 'What?')
        qn2 = Question(2, 'Who?')
        qn3 = Question(3, 'Where?')
        qn4 = Question(4, 'Why?')
        test_survey = Survey([qn1, qn2, qn3])
        assert test_survey.__contains__(qn1) is True
        assert test_survey.__contains__(qn4) is False

    def test_survey_str(self, questions) -> None:
        testsurvey = Survey([questions[0], questions[1], questions[2],
                             questions[3]])
        assert str(testsurvey) == 'Survey\n' \
                                  '1. why?\n' \
                                  '- a\n' \
                                  '- b\n' \
                                  '2. what?\n' \
                                  '3. really?\n' \
                                  '- Yes\n' \
                                  '- No\n' \
                                  '4. how?\n' \
                                  '- a\n' \
                                  '- b\n' \
                                  '- c'

    def test_get_questions(self, questions) -> None:
        testsurvey = Survey([questions[0], questions[1], questions[2],
                             questions[3]])
        assert testsurvey.get_questions() == [questions[0], questions[1],
                                              questions[2], questions[3]]

    def test_set_weight_valid(self, questions) -> None:
        testsurvey = Survey([questions[0], questions[1], questions[2],
                             questions[3]])
        assert testsurvey.set_weight(5, questions[0]) is True

    def test_set_weight_invalid(self, questions) -> None:
        testsurvey = Survey([questions[0], questions[1], questions[2]])
        assert testsurvey.set_weight(5, questions[3]) is False

    def test_set_criterion_valid(self, criteria, questions) -> None:
        testsurvey = Survey([questions[0], questions[1], questions[2],
                             questions[3]])
        assert testsurvey.set_criterion(criteria[0], questions[0]) is True

    def test_set_criterion_invalid(self, criteria, questions) -> None:
        testsurvey = Survey([questions[0], questions[1], questions[2]])
        assert testsurvey.set_criterion(criteria[1], questions[3]) is False

    def test_score_students(self, survey_, students_with_invalid_answers) -> \
            None:
        score = survey_.score_students(students_with_invalid_answers)
        assert round(score, 2) == 0.0

    def test_score_grouping_sa_grouping(self, survey_, sa_grouping) -> None:
        score = survey_.score_grouping(sa_grouping)
        assert round(score, 2) == 2.29

    def test_score_grouping_alpha_grouping(self, survey_,
                                           alpha_grouping) -> None:
        score = survey_.score_grouping(alpha_grouping)
        assert round(score, 2) == 2.0

###############################################################################
# Task 10 Test cases
###############################################################################
class TestGrouper:
    def test_grouper_len(self) -> None:
        stu1 = Student(1, 'a')
        stu2 = Student(2, 'b')
        stu3 = Student(3, 'c')
        test_group = Group([stu1, stu2, stu3])
        assert len(test_group) == 3

    def test_grouper_contains(self) -> None:
        stu1 = Student(1, 'a')
        stu2 = Student(2, 'b')
        stu3 = Student(3, 'c')
        stu4 = Student(1, 'e')
        test_group = Group([stu1, stu2, stu3])
        assert test_group.__contains__(stu4)

    def test_alpha_make_grouping(self, course_with_students_with_answers,
                                 other_alpha_grouping,
                                 survey_) -> None:
        grouper_ = grouper.AlphaGrouper(3)
        grouping = grouper_.make_grouping(course_with_students_with_answers,
                                          survey_)
        compare_groupings(grouping, other_alpha_grouping)

    def test_greedy_make_grouping(self, course_with_students_with_answers,
                                  greedy_grouping, survey_) -> None:
        grouper_ = grouper.GreedyGrouper(2)
        grouping = grouper_.make_grouping(course_with_students_with_answers,
                                          survey_)
        compare_groupings(grouping, greedy_grouping)