Test Driven Development (TDD) is an approach to software development and refers to the practice of creating test cases first and developing the solution further from the initial test cases. For every small functionality of a solution TDD starts with designing and developing tests.
In traditional software development, developers use this approach to develop functionality enclosed in test cases to identify which code succeeds and which code fails. Failed test cases require new logic to be implemented or existing logic to be enhanced for the test to succeed. If an automated test fails to avoid duplicated code, the TDD framework indicates developers to write new code.
Tests are requirement conditions that need to be validated as either passed or failed. The aim of TDD is to keep code simple, accountable, and bug-free.
For TDD to be successful and robust, all test cases should be defined so that they can be included in development as validation. It is possible for new test cases to present themselves throughout the testing process, however, defining as many cases up front as possible guarantees that all requirements are covered. The process of implementing TDD in a solution is defined in Figure 1.
Figure 1: Test Driven Development Process
There are many different types of tests that could form part of a TDD strategy - some of which are listed here. The benefit of creating code units and testing through unit tests is largely attributed to code reusability and the ability to refactor code easily. By using UiPath's built-in functionality, TDD may very well become the new way of thinking when developing automations.
The focus of this series is on robotic process automation (RPA) testing, however, the methodology outlined below may also be used for application testing, together with the IDE used for the application development. Through the implementation of application testing, the application’s code will become a ‘black-box’ for a UiPath Studio project.
The definition of terms relating to testing may vary or be used interchangeably in different environments and with this in mind, the definition of the terms used within this post are as it follows:
1. Functional requirement: This refers to the functionality that a system must have and how the functions should be performed. For example, logging into a system, extracting specific information, or sending a report.
2. Non-functional requirement: A non-functional requirement refers to the aspects of a solution that have an impact on the quality attributes of a solution. These requirements are deemed as supportive requirements to ensure that the functional requirements are implemented appropriately and according to good software practices. For example, capturing information about any errors that may occur or monitoring the performance of a solution.
3. Code unit: This refers to a code block that contains code which addresses and caters for a functional requirement. Non-functional requirements that support the functional requirement which a code unit is responsible for may also be addressed within the code unit if the code only accommodates for that specific functional requirement. Code units are invoked and validated by unit tests, while being implemented within the main solution flow.
4. Unit Test: This refers to the testing of a sequence/workflow in the solution that stands alone as a code unit and is responsible/accountable for a singular piece of functionality. The functionality that the code unit is responsible for should ideally be linked to one functional requirement. There are cases where multiple non-functional requirements may relate to one a functional requirement. In such cases, it is acceptable to accommodate for multiple requirements in one code unit.
5. Test Case: In this case, they are implemented as conditions that validate the appropriate implementation of a requirement. Test cases may be created to test multiple different conditions or fulfil multiple acceptance criteria (as test steps) within a test case. This also means that multiple test cases may be used to validate the outcomes of a single unit test.
6. Test Scenario (or Test Sets, in the context of UiPath): This describes a group of test cases related to a single unit test. All test cases that are used to validate the outcome of a unit test are consolidated within the test scenario and, together, determine the success or failure of the unit test.
The most important part of TDD is understanding what should be achieved with the tests that are being created. Before identifying what test to create, the requirements need to be highlighted and understood so that test cases can be developed and associated to unit tests as test scenarios per unit test.
The business problem, in this case (from a high-level perspective), revolves around not being able to adequate identify the breakeven point of automation solutions. A process currently exists which monitors the telemetry within an automation environment. The current telemetry process calculates the time saved as well as the cost of the process. The cost of the process should be compared to the total cost spent on automation to identify how many years/runs the process needs to be in production for before a breakeven point is reached.
The main functional requirement that is highlighted by the business problem is the need for a breakeven calculation to be added as an enhancement to the existing process. When this functional requirement is broken down further, the calculation, input parameters and output parameters are defined as follows:
The calculation used to determine breakeven point would be:
n = log(1+i)(PiA+1)n = log1+i(PiA+1)
>> P (Total cost)
>> A (Cost per year) >> i (Annual percentage increase)
>> n (Number of years)
>> r (Number of runs)
The input parameters of this calculation would be:
1. Total cost
2. Cost per year
3. Annual percentage increase
4. Number of times the process runs per year
The output parameters of this calculation would be:
1. Number of years
2. Number of runs
3. The non-functional requirements of the business problem are outlined as follows:
The solution must run in a timely manner, providing results in under 30 seconds.
The output arguments should be reliable and should not depict a negative number of runs or years.
Test cases are, essentially, conditions that validate that a requirement has been implemented correctly by evaluating that the correct output is generated from the input data provided. Test cases may be created to test multiple different conditions or fulfill multiple acceptance criteria (as test steps) within a test case. For this implementation, a single test case will contain a single condition. Multiple test cases may be used to validate the outcomes of a single unit test. These test cases are grouped together to form a test scenario relating to a specific unit test.
An important aspect of TDD is negative testing. Since the approach to TDD is to make a test fail, negative tests should be created to ensure that as many potential failures are catered for as possible. This helps form a more robust code unit.
Test Case #1: Negative input values
The aim of this test case is to test the outcome of any negative values parsed through to the unit test as negative values (values less than 0).
Test Case #2: Positive input values
The aim of this test case is to test the outcome of all values parsed through to the unit test as positive values (values greater than but not equal to 0).
Test Case #3: Input values of 0
The aim of this test case is to test the outcome of any values parsed through to the unit test with a value of 0.
Unit tests help in ensuring that reliable code blocks/units are created and adhere to any governance, standards, and policies that exist within an automation environment. Unit tests also help uphold the consistency of quality delivered by a solution.
Unit tests are built as sequences that only test the outcomes of a code unit by parsing through input arguments and retrieving output arguments—unit tests shouldn't contain any functionality. All functionality should be catered for in the code unit. The code unit would be a sequence or workflow that's invoked by the main process flow.
The implementation of the TDD, holistically in a solution is represented in the figure below:
It is important to note that tests are invoked by a separate code block specifically responsible for testing the code units. The tests are in no way linked to or invoked by Main. Main invokes the same code units that are invoked by the tests, however, Main is not involved in TDD. Mainly what is executed from a published RPA bot, whereas tests are run during development and debugging.
Now that the conceptual foundation of understanding the use of TDD within automation solutions has been laid, we can dive into practically implementing TDD in the tutorial: How to Implement TDD in the Development of Automation Solution.
Review UiPath and earn gift cards. Join us and help other professionals learn from your experience with UiPath. Your review can be anonymous.