Functional Models - aapowers/BroadleafCommerce GitHub Wiki
Functional models are visual representations of functional requirements. They are often represented as diagrams, such as finite-state machines, sequence diagrams, and interaction diagrams. However, they may also be represented as formal logic, statistical descriptions, and more.
Functional models can be useful for testing, especially when they are both compact and predictive - compact enough to be reasoned about, but predictive enough to predict the behavior of a system in any given state. They improve our understanding of a given system's requirements, which can help us to notice when a system's requirements are unclear for a given state, they help us decide what test cases need to be written, and they provide a meaningful criterion for the completeness of our testing. For example, we can decide that our testing is complete when we have test cases that visit every node, edge, branch, or path in a state diagram.
The Load User Feature can be described with the following:
The Load User Feature should take a string input from the user, which is the username. If the user doesn not exist in the system, it should return an error. If the user does exist, then it should return with some information about the user including:
- user ID
- username
- password
- active or non-active status
- expired or non-expired status, which will always be not expired since expiring is a future feature
- whether or not the user needs to reset their password
- if the account is locked, which will always be not locked since locking is a future feature
- permission roles, which will always include ROLE_USER, but may include other roles as well. If it does not contain ROLE_USER, that role must be added to the user so that it is included.
To better understand the requirements, we can create a functional model. Below is a state diagram for the Load User Feature.
There are ten nodes in the diagram, each representing a state that our application can be in. The edges (arrowed lines) are transitions that the application can take from one state to another.
For clarity, let's walk through an example. In this example, zdavid is the username of an existing, deactivated user, who does not need to change their password and does not have ROLE_USER in their roles. Beginning with start, the application goes into Node 1 and is ready to receive a username input. After the user inputs zdavid, the application is in Node 2 with zdavid stored as the username. If a user with the username zdavid did not exist, the application would throw an exception and return to the ready state. However, a user with the username zdavid does exist, is deactivated, and does not need a password change, so the application follows the edge to Node 3. In Node 3, zdavid is still stored as the username, and the user info for zdavid is stored. Because zdavid does not have the ROLE_USER in their list of roles, the application adds ROLE_USER to the list of roles and continues to Node 4. In Node 4, zdavid is still stored as the username, and the user info for zdavid is still stored, but now it includes their roles as well. The application then returns the user info that it gathered for zdavid and is ready to receive another input.
We can use this state diagram to decide what unit tests to write and when to stop. For example, we can write unit tests to make sure that every node in the graph is visited at least once. Another option is to write unit tests to make sure that every edge in the graph is traversed. For this feature, it would be better to have the goal of edge-coverage, instead of node-coverage. This is because traversing all the edges automatically covers all the nodes, whereas covering all the nodes does not necessarily traverse all the edges. It is additionally important that we aim for edge-coverage on this feature, because it is possible to skip edges that seem like likely spots for bugs (e.g. adding ROLE_USER to roles, throwing an exception when a user does not exist) and still reach all the nodes.
To cover every edge in the graph, we must write the following test cases:
Test | Edges Covered | Description |
---|---|---|
1 | 1:2, 2:1 | user does not exist |
2 | 1:2, 2:3, 3:4(a), 4:1 | user is deactivated, does not need to change their password, roles contains ROLE_USER |
3 | 1:2, 2:3, 3:4(b), 4:1 | user is deactivated, does not need to change their password, roles does not contain ROLE_USER |
4 | 1:2, 2:5, 5:6(a), 6:1 | user is deactivated, needs to change their password, roles contains ROLE_USER |
5 | 1:2, 2:5, 5:6(b), 6:1 | user is deactivated, needs to change their password, roles does not contain ROLE_USER |
6 | 1:2, 2:7, 7:8(a), 8:1 | user is active, needs to change their password, roles contains ROLE_USER |
7 | 1:2, 2:7, 7:8(b), 8:1 | user is active, needs to change their password, roles does not contain ROLE_USER |
8 | 1:2, 2:9, 9:10(a), 10:1 | user is active, does not need to change their password, roles contains ROLE_USER |
9 | 1:2, 2:9, 9:10(b), 10:1 | user is deactivated, does not need to change their password, roles does not contain ROLE_USER |
The nine test cases described in the table above can be found in UserDetailsServiceImplTest.java
, which is
available on GitHub.
To run the tests, open the file UserDetailsServiceImplTest.java
, right click on the class name,
and click Run UserDetailsServiceImplTest
.