E1853 write unit tests for menu: Difference between revisions
(Fixed moooooore formatting.) |
|||
Line 50: | Line 50: | ||
===Test Plan=== | ===Test Plan=== | ||
In order to fully test menu.rb, we composed a total of 29 tests, 12 for the Node class and 17 for the Menu class. | In order to fully test menu.rb, we composed a total of 29 tests, 12 for the Node class and 17 for the Menu class. | ||
*'''Node''' | *'''Node''' | ||
*# Node#setup - Node#setup appropriately sets a Node's parent_id, name, id, and label to those of the MenuItem passed to it as an argument. | *# Node#setup - Node#setup appropriately sets a Node's parent_id, name, id, and label to those of the MenuItem passed to it as an argument. |
Revision as of 17:01, 27 October 2018
E1853. Write Unit Tests for Menu.rb
Introduction
Team
Zhewei Hu (zhu6) (mentor)
- Joe Giallo (jfgiall2)
- Senthil Sankar (ssankar9)
- Vato Maskhulia (vmaskhu)
Task and Results
Task
For this project, our task was to achieve at least 90% code coverage on the menu.rb model.
Results
We ended up with 100% code coverage, and mutation testing on our test suite yielded 90.21% mutant-slaying capabilities. We believe this is a demonstration of the exemplary thoroughness of our test suite, and that these changes should be pulled in to the Expertiza master branch. This image shows our test suite running.
This image shows an abbreviated version of /coverage/index.html page which is generate after running rspec. It shows us achieving 100% code coverage.
This image shows our mutation score. Of 531 mutations, we killed 479 and 52 were left alive. This is a mutation coverage of 90.21%. While we will leave it to the TA to verify this claim, examination of the living mutants shows that many of the mutations replaced lines with equivalent code (fetching a variable as opposed to just accessing it, or substituting equivalent variables for one another). Similarly, killing the remainder of the mutants would require binding our tests to implementation, to ensure that particular methods get called. As such, we believe our mutation score is quite good.
Effected Files
The files involved in this commit are:
- menu.rb - the model being tested. Contains the menu class and the node class.
- menu_spec.rb - the spec file for menu. Contains all of our tests.
- spec_helper.rb - we added a limit on how long tests can take to run, as one of the mutations which was generated had infinite runtime.
Video of Test Suite Running
A video of our test suite running properly can be found here!
Background
Expertiza
Expertiza is an open source web application based on Ruby on Rails framework. It provides instructors with the ability to create, customize, and assign projects. It provides students with the ability to form teams and collaborate on these assignments, submit those assignments, as well as review others and be reviewed on their work.
Menus and Nodes
Menus (and their sub-component, Nodes) provide the foundation for navigation within the Expertiza platform. With the role of the user as input, they present to the user all possible options a user of their role can have from the top of each Expertiza screen. The Menu is critical for navigating Expertiza and exposing the correct functionality to the correct users.
Motivation
Testing is critical to ensuring that software is functional. A robust test suite which tests the returns of query messages and the side effects of command messages will help expedite development by quickly ensuring nothing has broken when changes are made. While manually testing the behavior of the menu is quite straight-forward, automating the underlying mechanics of the menu, which are abstract and generic, is a good best practice to prove that the system for generating menus is not broken.
Test Plan
In order to fully test menu.rb, we composed a total of 29 tests, 12 for the Node class and 17 for the Menu class.
- Node
- Node#setup - Node#setup appropriately sets a Node's parent_id, name, id, and label to those of the MenuItem passed to it as an argument.
- Node#setup - When MenuItem has a ControllerAction, Node#setup(MenuItem) makes Node's controller_action_id becomes that ControllerAction's id.
- Node#setup - When MenuItem has a ControllerAction, Node#setup(MenuItem) makes Node's url become ControllerAction's url_to_use.
- Node#setup - When MenuItem's ControllerAction has a Controller, Node#setup(MenuItem) makes Node's site_controller_id equal to Controller's id.
- Node#setup - When MenuItem's ControllerAction has no url_to_use, Node#setup(MenuItem) makes Node's url equal to "/#{controller.name}/#{controller_action.name}", the controller_action's name appended to the controller's name.
- Node#setup - When MenuItem has a ContentPage, Node#setup(MenuItem) makes Node's content_page_id equal to ContentPage's id.
- Node#setup - When MenuItem has a ContentPage, Node#setup(MenuItem) makes Node's url equal to ContentPage's name.
- Node#site_controller - #site_controller sets the @site_controller instance variable.
- Node#controller_action - #controller_action sets the @controller_action instance variable.
- Node#content_page - #content_page sets the @content_page instance variable.
- Node#add_child - #add_child updates Node's children
- Node#add_child - #add_child adds multiple children to Node's children.
- Menu
- Menu#select - it returns if select(name) is not in @by_name
- Menu#select - when name is in @by_name, Menu.selected is equal to name, @crumbs ends with the lowest level node and begins with the highest level node, and #selected? returns true for all nodes from the selected node to the root.
- Menu#get_item - returns nil when id is not in @by_id
- Menu#get_item - returns the equivalent item when id is in @by_id (item is not identical, but all components of item are the same).
- Menu#get_menu - returns nil if level has no children.
- Menu#get_menu - return children of a level if level has children.
- Menu#get_menu - return all nodes except root if level is root.
- Menu#selected - returns root if nothing has been selected.
- Menu#selected - returns the name of the last selected menu_item.
- Menu#selected? - if nothing has been selected, selected?(root_node) returns true.
- Menu#selected? - if a node has been selected, selected? returns true for selected node and all its parents.
- Menu#selected? - if there are no menu items corresponding to the permission's of the role provided to menu, selected? does not return true.
- Menu#selected? - if MenuItem returns an empty array, selected? returns false for all input.
- Menu#crumbs - When top-level menu item is selected, crumbs has length one.
- Menu#crumbs - When top-level menu item is selected, crumbs has the root node's id.
- Menu#crumbs - When bottom-level menu item is selected, crumbs has two crumbs (assuming lowest level is two).
- Menu#crumbs - When bottom-level menu item is selected, crumbs are ordered from child to parent and all are present up to root.
Design and Impact
Design
Because of the nature of our assignment, our only design decisions were made with respect to making our Rspec tests as clean and readable as possible. We did not modify the application code itself. That being said, the Menu model is highly compositional, and it touches on MenuItem, ControllerAction, Controller, ContentPage, and Role (in addition to Node, which is in the same model). As a result, one of our main objectives was to make sure that we were transparent about all the different models which need to exist and how they interact with each other, in order for the Menu model to work.
We address this issue by neatly creating all of the mock objects that we need in 'let' blocks before the tests, and explaining any of the tricky nuances (like how MenuItems and Roles interact using the role_(user_type).yml file). We also add additional comments to some of the more complex test cases (#select) to explain why we're expecting the particular side effects we're expecting, when our 'it' and 'context' block descriptions cannot easily convey all of the complexities.
Here is a snapshot of the Node unit tests, which shows proper utilization of Rspec best practices. We define all of our necessary mock objects. We utilize short, coherent expectations. We use contexts to provide more information about the branches in a function that we are testing. We avoid binding ourselves to implementation by stubbing methods which are called in other classes.
Impact
In the future, it will be very easy to test whether changes to the menu model have broken the original functionality. It will also be easy to add more tests to raise our test suite's mutant-killing rank, as we have done the hard part of defining all the mock objects and prototyping the relations they all have with each other in this initial set of tests.