E1853 write unit tests for menu

From Expertiza_Wiki
Revision as of 23:15, 30 October 2018 by Jfgiall2 (talk | contribs) (→‎Design)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

E1853. Write Unit Tests for Menu.rb


Introduction

Team

Zhewei Hu (zhu6) (mentor)

  • Joe Giallo (jfgiall2)
  • Senthil Sankar (ssankar9)
  • Vato Maskhulia (vmaskhu)

Task

For this project, our task was to achieve at least 90% code coverage on the menu.rb model.

Background

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
    1. 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.
    2. Node#setup - When MenuItem has a ControllerAction, Node#setup(MenuItem) makes Node's controller_action_id becomes that ControllerAction's id.
    3. Node#setup - When MenuItem has a ControllerAction, Node#setup(MenuItem) makes Node's url become ControllerAction's url_to_use.
    4. Node#setup - When MenuItem's ControllerAction has a Controller, Node#setup(MenuItem) makes Node's site_controller_id equal to Controller's id.
    5. 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.
    6. Node#setup - When MenuItem has a ContentPage, Node#setup(MenuItem) makes Node's content_page_id equal to ContentPage's id.
    7. Node#setup - When MenuItem has a ContentPage, Node#setup(MenuItem) makes Node's url equal to ContentPage's name.
    8. Node#site_controller - #site_controller sets the @site_controller instance variable.
    9. Node#controller_action - #controller_action sets the @controller_action instance variable.
    10. Node#content_page - #content_page sets the @content_page instance variable.
    11. Node#add_child - #add_child updates Node's children
    12. Node#add_child - #add_child adds multiple children to Node's children.
  • Menu
    1. Menu#select - it returns if select(name) is not in @by_name
    2. 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.
    3. Menu#get_item - returns nil when id is not in @by_id
    4. 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).
    5. Menu#get_menu - returns nil if level has no children.
    6. Menu#get_menu - return children of a level if level has children.
    7. Menu#get_menu - return all nodes except root if level is root.
    8. Menu#selected - returns root if nothing has been selected.
    9. Menu#selected - returns the name of the last selected menu_item.
    10. Menu#selected? - if nothing has been selected, selected?(root_node) returns true.
    11. Menu#selected? - if a node has been selected, selected? returns true for selected node and all its parents.
    12. Menu#selected? - if there are no menu items corresponding to the permission's of the role provided to menu, selected? does not return true.
    13. Menu#selected? - if MenuItem returns an empty array, selected? returns false for all input.
    14. Menu#crumbs - When top-level menu item is selected, crumbs has length one.
    15. Menu#crumbs - When top-level menu item is selected, crumbs has the root node's id.
    16. Menu#crumbs - When bottom-level menu item is selected, crumbs has two crumbs (assuming lowest level is two).
    17. 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 an example of how we mock objects for the Node unit tests.

Here is a snapshot of the Node unit tests, which shows proper utilization of Rspec best practices. 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.

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!

E1853 Menu Unit Tests Running (Hosted on Youtube)