class TestClassStr: @pytest.mark.parametrize( 'class_object,' 'expected_str', [ (Class(name='name_only_class'), f"Class {'name_only_class'}, with id=None, containing 0 students." ), (Class(class_id='some class id', name='name_only_class with id'), f"Class {'name_only_class with id'}, with id={'some class id'}, containing 0 students." ), (Class.from_dict(test_full_class_data_set['json_dict_rep']), f"Class {Class.from_dict(test_full_class_data_set['json_dict_rep']).name}, " f"with id={Class.from_dict(test_full_class_data_set['json_dict_rep']).id}, " f"containing {len(Class.from_dict(test_full_class_data_set['json_dict_rep']).students)} students, " f"with names: {', '.join([student.name for student in Class.from_dict(test_full_class_data_set['json_dict_rep']).students])}." ), (Class(class_id='some class id', name='small_class with id', students=[Student(name='one'), Student(name='two') ]), f"Class {'small_class with id'}, " f"with id={'some class id'}, " f"containing 2 students, " f"with names: one, two."), ]) def test_str(self, class_object, expected_str): assert str(class_object) == expected_str
def test_take_class_data_input(self, monkeypatch): test_class_name = 'my test class' test_take_student_name_input_returns = ['test_student', 'END'] take_student_avatar_return = 'my_student_avatar.jpg' test_class = Class( name=test_class_name, students=[ Student(name=test_take_student_name_input_returns[0], avatar_id=take_student_avatar_return), ]) test_take_student_name_input_return = ( name for name in test_take_student_name_input_returns) def mocked_take_student_name_input(new_class): if new_class.name != test_class_name: raise ValueError return next(test_take_student_name_input_return) def mocked_take_student_avatar(new_class, student_name): if (new_class.name, student_name) != ( test_class_name, test_take_student_name_input_returns[0]): raise ValueError return take_student_avatar_return monkeypatch.setattr(class_functions, 'take_student_avatar', mocked_take_student_avatar) monkeypatch.setattr(class_functions, 'take_student_name_input', mocked_take_student_name_input) assert take_class_data_input( test_class_name).json_dict() == test_class.json_dict()
def setUp(self) -> None: self.test_class_name = 'my test class' self.test_student_name_input_returns = ['test_student', 'END'] self.take_student_avatar_return = 'my_student_avatar.jpg' self.test_class = Class( name=self.test_class_name, students=[ Student(name=self.test_student_name_input_returns[0], avatar_filename=self.take_student_avatar_return), ])
def test_class_name_only(): """Returns empty class instantiated with name only.""" test_class_name_only = Class( class_id=test_class_name_only_data_set['json_dict_rep']['id'], name=test_class_name_only_data_set['json_dict_rep']['name']) # Add attributes to test expected output. test_class_name_only.json_str_rep = test_class_name_only_data_set[ 'json_str_rep'] test_class_name_only.json_dict_rep = test_class_name_only_data_set[ 'json_dict_rep'] return test_class_name_only
class TestClassRepr: @pytest.mark.parametrize('class_object', [ Class(name='name_only_class'), Class(name='name only class with id', class_id='a class id'), Class.from_dict(test_full_class_data_set['json_dict_rep']), ]) def test_repr(self, class_object): assert repr(class_object) == ( f'{class_object.__class__.__module__}.{class_object.__class__.__name__}(' f'id={class_object.id if class_object.id else None !r}, ' f'name={class_object._name!r}, ' f'path_safe_name={class_object._path_safe_name!r}, ' f'students={class_object.students!r}' f')')
class TestComposeClasslistDialogue: @pytest.mark.parametrize( 'class_data, create_blank_class', [ ([Class.from_dict(test_full_class_data_set['json_dict_rep'])], []), ([Class(name='Empty class with no students') ], [True]), # Empty class created pytest.param( [Class(name='class with no students')], [], marks=pytest.mark.xfail( reason="No values left to return from blank_class_dialogue." )), ( [ Class(name=Class.from_dict( test_full_class_data_set['json_dict_rep']).name ), # Empty class, then full class. Class.from_dict(test_full_class_data_set['json_dict_rep']) ], [False]), ([Class(name="Empty class"), Class(name="Empty class") ], [False, True]), # Empty class refused then accepted. ]) def test_compose_classlist_dialogue_full_class( self, monkeypatch, class_data, create_blank_class, test_full_class, ): class_data_to_return = (test_class for test_class in class_data) def mocked_take_class_data_input(class_name): if class_name not in [ test_class.name for test_class in class_data ]: raise ValueError return next(class_data_to_return) blank_class_returns = (chosen_option for chosen_option in create_blank_class) def mocked_blank_class_dialogue(): # Will raise StopIteration if no values left to return (or none to begin with). return next(blank_class_returns) def mocked_class_data_feedback(test_class): if test_class not in class_data: raise ValueError monkeypatch.setattr(class_functions, 'take_class_data_input', mocked_take_class_data_input) monkeypatch.setattr(class_functions, 'blank_class_dialogue', mocked_blank_class_dialogue) monkeypatch.setattr(class_functions, 'class_data_feedback', mocked_class_data_feedback) assert compose_classlist_dialogue( class_data[0].name).json_dict() == class_data[-1].json_dict()
def write_classlist_to_file(current_class: Class): """ Write classlist data to disk as JSON dict, according to Class object's Class.json_dict and Class.to_json_str methods. CAUTION: conversion to JSON will convert int/float keys to strings, and keep them as strings when loading. :param current_class: Class object :return: Path """ class_name = current_class.name data_filename = class_name + CLASSLIST_DATA_FILE_TYPE classlist_data_path = CLASSLIST_DATA_PATH.joinpath(class_name, data_filename) json_class_data = current_class.to_json_str() # Make data path if it doesn't exist. classlist_data_path.parent.mkdir(parents=True, exist_ok=True) with open(classlist_data_path, 'w') as classlist_file: classlist_file.write(json_class_data) return classlist_data_path
def test_select_student(self, monkeypatch, selected_student_name, selected_student_students_index): test_class_students = [ Student(name='one'), Student(name='two'), Student(name='three') ] test_class = Class(name='some_class', students=test_class_students) test_student_options = {1: 'one', 2: 'two', 3: 'three'} def mocked_display_student_selection_menu(class_options): if class_options != test_student_options: raise ValueError # Ensure called with correct arg. return None def mocked_take_student_selection(class_options): if class_options != test_student_options: raise ValueError # Ensure called with correct arg. return selected_student_name monkeypatch.setattr(class_functions, 'display_student_selection_menu', mocked_display_student_selection_menu) monkeypatch.setattr(class_functions, 'take_student_selection', mocked_take_student_selection) assert select_student( test_class) == test_class_students[selected_student_students_index]
def load_class(self, class_id: Any) -> Class: """ Load class from database using primary key class.id. :param class_id: :return: Class """ with self.session_scope() as session: class_data = session.query(self.Class, self.Student).filter( self.Class.id == self.Student.class_id).filter( self.Student.class_id == class_id).all() if class_data: class_id, class_name = class_data[0][0].id, class_data[0][ 0].name students_list = [ Student( student_id=student.id, name=student.name, class_id=class_data.id, avatar_id=student.avatar_id, ) for class_data, student in class_data ] else: # Empty class empty_class = session.query( self.Class).filter(self.Class.id == class_id).one() students_list = [] class_id, class_name = empty_class.id, empty_class.name return Class(class_id=class_id, name=class_name, students=students_list)
def test_load_class(self, request, database_backend, class_data): """Class loaded has same data as that saved in db, with class/student ids.""" test_database = request.getfixturevalue(database_backend) preexisting_class = request.getfixturevalue(class_data) # Create test NewClass object, mocking avatar_files. preexisting_class = NewClass.from_dict(preexisting_class.json_dict()) for student in preexisting_class: if student.avatar_id: Path(preexisting_class.temp_avatars_dir, student.avatar_id).write_text(student.avatar_id) # Create class in db: test_database.create_class(preexisting_class) # Find class id, load class to verify: classes = test_database.get_classes() test_full_class_id = classes[ 0].id # As the only class will be first item. # Loaded class will have ids: test_loaded_class_with_student_ids = Class.from_dict( preexisting_class.json_dict()) if isinstance(test_database, JSONDatabase): for student in test_loaded_class_with_student_ids.students: student.id = student.name if not isinstance(test_database, JSONDatabase): # This should be accurate for most sql databases. for test_id, student in enumerate( test_loaded_class_with_student_ids.students, start=1): student.id = test_id assert test_database.load_class(test_full_class_id).json_dict( ) == test_loaded_class_with_student_ids.json_dict()
def setUp(self): self.no_student_name = '' self.blank_student_name = '_' self.preexisting_student_name = 'this student already exists in the class' self.valid_new_student_name = 'this is a valid student_name' self.invalid_student_name_response = 'Please enter a valid student name.' self.preexisting_student_response = 'This student is already a member of the class.' self.test_case_inputs = [ self.no_student_name, self.blank_student_name, self.preexisting_student_name, self.valid_new_student_name, ] self.printed_feedback = [ self.invalid_student_name_response, self.invalid_student_name_response, self.preexisting_student_response, ] self.test_class = Class( name='my_test_class', students=[Student(name=self.preexisting_student_name)])
def take_class_data_input(class_name: str): """ Take student names, avatars, return Class object. :param class_name: str :return: Class """ new_class = Class(name=class_name) while True: student_name = take_student_name_input(new_class) if student_name.upper() == 'END': break avatar_filename = take_student_avatar(class_name, student_name) new_class.add_student(name=student_name, avatar_filename=avatar_filename) return new_class
def test_create_class(self, request, empty_sqlite_database, class_data): test_database = empty_sqlite_database test_class_data = request.getfixturevalue(class_data) # Create test NewClass object, mocking avatar_files. test_class = NewClass.from_dict(test_class_data.json_dict()) for student in test_class: if student.avatar_id: Path(test_class.temp_avatars_dir, student.avatar_id).write_text(student.avatar_id) # no students or class in empty db: assert not test_database._connection().cursor().execute( """SELECT * FROM class""").fetchall() assert not test_database._connection().cursor().execute( """SELECT * FROM student""").fetchall() # Create class in db: assert test_database.create_class(test_class) is None # Find class id, load class to verify: classes = test_database.get_classes() test_class_id = classes[0].id # Class will have ids: test_loaded_class_with_student_ids = Class.from_dict( test_class.json_dict()).json_dict() for test_id, student in enumerate( test_loaded_class_with_student_ids['students'], start=1): student['id'] = test_id assert test_database.load_class( # NB Returned object will be Class, not NewClass: test_class_id).json_dict() == test_loaded_class_with_student_ids
class TestAssembleChartData: @pytest.mark.parametrize('class_from_create_class', [Class.from_dict(test_full_class_data_set['json_dict_rep']), # Pass in test_class NewClass.from_dict(test_full_class_data_set['json_dict_rep']) # NewClass obj ]) def test_assemble_chart_data(self, monkeypatch, class_from_create_class): test_chart_name = 'my_chart' test_chart_filename = test_chart_name mock_score_avatar_dict = {'scores': 'list of avatars'} mock_chart_params = {'some': 'chart_params'} def mocked_take_score_data(class_obj): assert class_obj is class_from_create_class return mock_score_avatar_dict def mocked_take_chart_name(): return test_chart_name def mocked_clean_for_filename(chart_name): assert chart_name == test_chart_name return test_chart_name def mocked_set_chart_params(): return mock_chart_params monkeypatch.setattr(create_chart, 'take_score_data', mocked_take_score_data) monkeypatch.setattr(create_chart, 'take_chart_name', mocked_take_chart_name) monkeypatch.setattr(create_chart, 'clean_for_filename', mocked_clean_for_filename) monkeypatch.setattr(create_chart, 'set_chart_params', mocked_set_chart_params) assert assemble_chart_data(class_from_create_class) == ( test_chart_name, test_chart_filename, mock_score_avatar_dict, mock_chart_params)
def test_name_setter(self): test_name = "The Knights of the Round-table: we don't say 'Ni!'" test_changed_name = 'Adaptable Knights: We now say Ni!, but we dont have to.' test_changed_path_safe_name = 'Adaptable_Knights__We_now_say_Ni___but_we_dont_have_to_' test_class = Class(test_name) # Original test_class attributes not equal to changed: assert (test_class.name, test_class.path_safe_name) != ( test_changed_name, test_changed_path_safe_name) # Change name test_class.name = test_changed_name assert (test_class.name, test_class.path_safe_name) == (test_changed_name, test_changed_path_safe_name)
def test_path_safe_name_getter_mocking_calls(self, monkeypatch): mock_path_safe_name = "Something completely different." def mocked_clean_for_filename(class_name): return mock_path_safe_name monkeypatch.setattr(class_, 'clean_for_filename', mocked_clean_for_filename) assert Class("The Knights of the Round-table: we don't say 'Ni!'" ).path_safe_name == mock_path_safe_name
def test_update_class(self, empty_json_database, test_full_class): test_json_database = empty_json_database test_class = Class(test_full_class.name, test_full_class.students) # create_class takes a NewClass object due to avatar moving machinery. test_class_new_class = NewClass(test_full_class.name, test_full_class.students) assert test_class.json_dict() == test_class_new_class.json_dict( ) # Ensure classes are the same. # Create class in database. test_json_database.create_class( NewClass(test_full_class.name, test_full_class.students)) # Ensure test_class in database test_class.id = test_class.name # Loaded class will have ids: test_loaded_class = test_full_class.json_dict() for student in test_loaded_class['students']: student['id'] = student['name'] assert test_json_database.load_class( test_class.name).json_dict() == test_loaded_class # Change class by adding student, update database: new_student = Student(name='new student') assert new_student not in test_class and new_student.name not in test_class # Confirm student not in class. test_class.add_student(new_student) assert test_json_database.update_class(test_class) is None # Look up name because new_student object itself is not in the loaded class object. assert new_student.name in test_json_database.load_class( test_class.name)
def test_take_score_data(self, monkeypatch): test_class = Class.from_dict(test_full_class_data_set['json_dict_rep']) mock_score_avatar_dict = {'scores': 'list of avatars'} def mocked_take_student_scores(scored_class): assert scored_class == test_class return mock_score_avatar_dict monkeypatch.setattr(take_chart_data_UI, 'take_student_scores', mocked_take_student_scores) assert take_score_data(test_class) == mock_score_avatar_dict
def load_class(self, class_id: int) -> Class: """ Load class from database using primary key class.id. :param class_id: :return: Class """ with self._connection() as conn: # Get class from db: class_data = conn.cursor().execute( """ SELECT class.id, class.name, student.id, student.name, student.avatar_id FROM class INNER JOIN student ON class.id=student.class_id WHERE class.id=?; """, (class_id, )).fetchall() if class_data: # Class id, name from first student row returned. class_id, class_name = class_data[0][:2] # Instantiate student objects: students_list = [ Student( student_id=student_id, name=student_name, class_id=class_id, avatar_id=avatar, ) for class_id, class_name, student_id, student_name, avatar in class_data ] # Handle empty class (no students = no rows returned: else: class_id, class_name = conn.cursor().execute( """ SELECT class.id, class.name FROM class WHERE class.id=?; """, (class_id, )).fetchone() students_list = [] conn.close() return Class(class_id=class_id, name=class_name, students=students_list)
class TestStudentClassId: """Test Student's Class Id""" @pytest.mark.parametrize( 'class_id_arg,', [17, # Integer eg sql db id 'some student_name', # JSON db id Class(name='Class used to represent complex object'), # Ensure 'Any' typing is accurate. ]) def test_id(self, class_id_arg): assert Student(name='Arthur, King of the Britons', class_id=class_id_arg).class_id == class_id_arg def test_student_id_default_arg(self): assert Student(name='Arthur, King of the Britons').class_id is None
def load_class_from_disk(class_name: str): """ Load class data from a class data ('.cld') file, return Class object. :param class_name: str :return: Class object """ class_data_filename = class_name + CLASSLIST_DATA_FILE_TYPE classlist_data_path = CLASSLIST_DATA_PATH.joinpath(class_name, class_data_filename) loaded_class = Class.from_file(classlist_data_path) return loaded_class
def setUp(self): self.test_class = Class.from_dict( test_full_class_data_set['json_dict_rep']) self.score_entry_instruction = ( f"\nEnter student scores for {self.test_class.name}: \n" f"Type score for each student, or '_' to exclude student, and press enter." ) self.newline_after_entry = '\n' self.print_calls = [ self.score_entry_instruction, self.newline_after_entry ] self.mock_score_avatar_dict = {'scores': 'list of avatars'}
def test_assemble_chart_data(self, monkeypatch): test_class = Class.from_dict(test_full_class_data_set['json_dict_rep']) test_class_name = test_class.name test_chart_name = 'my_chart' test_chart_filename = test_chart_name mock_score_avatar_dict = {'scores': 'list of avatars'} mock_chart_params = {'some': 'chart_params'} def mocked_select_classlist(): return test_class_name def mocked_load_class_from_disk(class_name): if class_name != test_class_name: raise ValueError return test_class def mocked_take_score_data(class_obj): if class_obj is not test_class: raise ValueError return mock_score_avatar_dict def mocked_take_chart_name(): return test_chart_name def mocked_clean_for_filename(chart_name): if chart_name != test_chart_name: raise ValueError return test_chart_name def mocked_set_chart_params(): return mock_chart_params monkeypatch.setattr(create_chart, 'select_classlist', mocked_select_classlist) monkeypatch.setattr(create_chart, 'load_class_from_disk', mocked_load_class_from_disk) monkeypatch.setattr(create_chart, 'take_score_data', mocked_take_score_data) monkeypatch.setattr(create_chart, 'take_chart_name', mocked_take_chart_name) monkeypatch.setattr(create_chart, 'clean_for_filename', mocked_clean_for_filename) monkeypatch.setattr(create_chart, 'set_chart_params', mocked_set_chart_params) assert assemble_chart_data() == (test_class_name, test_chart_name, test_chart_filename, mock_score_avatar_dict, mock_chart_params)
def test_name_setter_mocking_calls(self, monkeypatch): test_name = "The Knights of the Round-table: we don't say 'Ni!'" test_changed_name = 'Adaptable Knights: We now say Ni!, but we dont have to.' mock_changed_path_safe_name = "Adaptable_Knights: We're Niiiearly completely un!safe?!$" test_class = Class(test_name) # Original test_class attributes not equal to changed: assert (test_class.name, test_class.path_safe_name) != ( test_changed_name, mock_changed_path_safe_name) def mocked_clean_for_filename(class_name): return mock_changed_path_safe_name monkeypatch.setattr(class_, 'clean_for_filename', mocked_clean_for_filename) # Change name test_class.name = test_changed_name assert (test_class.name, test_class.path_safe_name) == (test_changed_name, mock_changed_path_safe_name)
def setUp(self): self.mock_CLASSLIST_DATA_PATH = Path('.') self.mock_CLASSLIST_DATA_FILE_TYPE = '.class_data_file' self.test_class_json_dict = test_full_class_data_set['json_dict_rep'] self.test_class_name = self.test_class_json_dict['name'] self.test_class_object = Class.from_dict(self.test_class_json_dict) # Build save file path self.test_class_filename = self.test_class_name + self.mock_CLASSLIST_DATA_FILE_TYPE self.test_class_data_path = self.mock_CLASSLIST_DATA_PATH.joinpath( self.test_class_name) self.test_class_data_file_path = self.test_class_data_path.joinpath( self.test_class_filename)
def test_full_class() -> Class: test_full_class = Class( class_id=test_full_class_data_set['json_dict_rep']['id'], name=test_full_class_data_set['json_dict_rep']['name']) for student in test_full_class_data_set['json_dict_rep']['students']: test_full_class.add_student(Student(**student)) test_full_class.json_str_rep = test_full_class_data_set['json_str_rep'] test_full_class.json_dict_rep = test_full_class_data_set['json_dict_rep'] return test_full_class
def test_take_student_scores(self, monkeypatch): """ Score data returned. Test with generic database. """ test_class = Class.from_dict(test_full_class_data_set['json_dict_rep']) # Gives no score to one student with and without an avatar. mocked_take_score_entry_return_values = [ 0, 1, 3, None, 50, 99, 100, 1, 2, 3, 4, None, 6, 7, 8 ] test_take_student_scores_return_value = { 0: [test_class.students[0]], # Cali 1: [ test_class.students[1], # Monty test_class.students[7] ], # Regina 3: [ test_class.students[2], # Abby test_class.students[9] ], # Alex # No score, not returned: None: [test_class.students[3], # Zach # test_class.students[11]], # Edgar 50: [test_class.students[4]], # Janell 99: [test_class.students[5]], # Matthew 100: [test_class.students[6]], # Olivia 2: [test_class.students[8]], # Ashley 4: [test_class.students[10]], # Melissa 6: [test_class.students[12]], # Danielle 7: [test_class.students[13]], # Kayla 8: [test_class.students[14]], # Jaleigh } score = (score for score in mocked_take_score_entry_return_values) def mocked_take_score_entry(student_name): return next(score) monkeypatch.setattr(take_chart_data_UI, 'take_score_entry', mocked_take_score_entry) assert take_student_scores( test_class) == test_take_student_scores_return_value
def transform_data(class_name: str, old_class_data: dict): """ Take class name (eg from old style cld filename), and loaded json dict, transform into a new-style Class object. :param class_name: str :param old_class_data: dict :return: Class object """ new_students = [] for student_name in old_class_data: if old_class_data[student_name][0] is None: new_students.append(Student(name=student_name)) else: new_students.append( Student(name=student_name, avatar_filename=old_class_data[student_name][0]), ) new_class = Class(name=class_name, students=new_students) return new_class
def test_take_student_name_input(self): """Test return on valid input after invalid inputs.""" preexisting_student_name = 'this student already exists in the class' test_class = Class(name='my_test_class', students=[Student(name=preexisting_student_name)]) valid_new_student_name = 'this is a valid student name' test_inputs = [ '', # no_student_name, '_', # blank_student_name, preexisting_student_name, # preexisting_student_name, valid_new_student_name, # valid_new_student_name, ] with patch('builtins.input') as mock_input: mock_input.side_effect = test_inputs assert take_student_name_input( test_class) == valid_new_student_name
def load_class(self, class_id: str) -> Class: """ Load a class from the database. Load class data from a class data ('.cld') file, return Class object. :param class_id: str - the class' name :return: Class object """ class_data_filename = class_id + self.class_data_file_type classlist_data_path = self.class_data_path.joinpath(class_id, class_data_filename) loaded_class = Class.from_file(classlist_data_path) # Append ids loaded_class.id = loaded_class.name # Student id not added: student cannot be found in the db by name alone. for student in loaded_class.students: student.id = student.name return loaded_class