NOTTINGHAM TRENT UNIVERSITY SCHOOL OF SCIENCE AND TECHNOLOGY Advanced Software Engineering Assignment 2 by Aras Butrimanskas in 2022 Table of Contents Task 1: Explaining Test-driven Development ............................................................ 2 Introduction ....................................................................................................... 2 Test-driven Development Cycles........................................................................... 2 Benefits ............................................................................................................ 3 Drawbacks......................................................................................................... 4 Conclusion ......................................................................................................... 4 Task 2: Applying Test-driven Development ............................................................... 5 Introduction ....................................................................................................... 5 Creating Automated Unit Tests ............................................................................. 6 Developing the Algorithms ................................................................................... 7 Bonus – Property-Based Tests .............................................................................. 8 Task 3: Reflecting on Experiences ........................................................................... 9 References ......................................................................................................... 10 1 Figure 1 - Unit Test Example .................................................................................. 6 Figure 2 - Test Formatting ...................................................................................... 6 Figure 3 - <=.005 Output using *1e2/1e2 ................................................................ 7 Figure 4 - decimalPoint Function ............................................................................. 7 Figure 5 - incomeTax function ................................................................................. 8 Figure 6 - Output of the Tests ................................................................................. 9 Task 1: Explaining Test-driven Development Introduction Test-driven Development (TDD) is a practice of software development in which unit tests are created first to cater to the functionality of the software prior to the code. That means the test cases are being deployed and only then the code is written according to the test cases to pass the test. This practice of software development has been used since around 1960s (Bhat and Nagappan, 2006), with the first recorded form of Testdriven Development being Mercury Space Program. However, Kent Beck is considered the pioneer of this practice and is additionally known as the creator of Agile and Extreme Programming methodology, who has written the first-ever test framework, which dates back to 1994, called SUnit (Agarwal and Deep, 2014)(Beck, 2003). Since both methodologies are currently widely accepted and work flawlessly together with Testdriven Development, it became very popular in various developer communities as well as corporations and remains in usage until now. Test-driven Development Cycles According to Kent Beck, the cycles of Test-driven Development are writing a test, making it run, and making it right (Beck, 2003). Even though the book is relatively outdated and is written to teach unit testing on SmallTalk programming language, the main principles of these cycles remain relevant to this day. 2 Writing a test is relatively obvious since Kent Beck does not use any wordplay in this statement. It is simply an act of writing a test that will pass when the specification of the functionality is met. Making it run is referred to as writing a very primitive code that passes the created test. This stage does not require to have a fully functional code of high quality, as it will be addressed during the last stage. Finally, making it right is referred to as refactoring the code. This is when a code of high quality should be implemented instead of the primitive one. According to the requirements of the software, the code might also be refactored further by removing duplicate code, rearranging the location of the code, splitting, or combining the methods, etc. It is a good idea to also re-run the tests to make sure that the tests still pass after refactoring the code. The cycle is then repeated for each additional functionality until all the requirements are met and all of the functionalities, according to the requirements, are implemented. Benefits There are a few major advantages to Test-driven Development method as opposed to the traditional method of developing first. By utilizing Test-driven Development, developers generate a code of much higher quality and cleaner design by creating the tests incrementally (Shull et al., 2010). This results in a source code that is easily maintainable and in turn tasks transferrable without issues among the members of the development team. Additionally, the tests that are created document the code indirectly, since they portray how the code should function or how various interfaces must be used. This helps get rid of the need to allocate the time for code documentation, which is very time-consuming when it comes to software development. 3 And lastly, it could be argued that Test-driven Development promotes higher productivity. This method helps developers stay confident with their code since they will naturally produce fewer bugs which will save time as a result. The study conducted by the students of the University of Kansas on the productivity of the Test-driven Development states that the team that practiced Test-driven Development has produced twice as many features as the team that practiced Test-last development (Janzen and Saiedian, 2006). Drawbacks On the other hand, there are a bunch of drawbacks to using Test-driven Development as opposed to Test-last development. Even though the code becomes easily maintainable after the code is complete, the tests have to be catered to the requirements if they change during the development. Due to this reason, the time it takes to implement the new requirements increases, as requirement refactoring ruins the existing tests, giving the developers almost twice the work as opposed to Test-last development. Surprisingly, another research conducted at the University of Jyvaskyla suggests that productivity actually decreases more so than increases contrary to what has been stated in the report from the University of Kansas (Kollanus, 2010). The studies concluded that Test-driven Development requires increased effort, which naturally increases the time taken to complete the development process. Conclusion Test-driven Development is a useful practice and a good alternative method to develop software whether it would be for a school project, a personal project, or the industry. However, looking back at the drawbacks as well as the benefits, it seems clear that Testdriven Development could be similar to a double-edged sword. There are reports that argue with and against this approach to increase the productivity of the development 4 process. Even though the productivity aspect is questionable, it is evident that production quality increases significantly when utilizing Test-driven development, however, with the price of additional time that must be dedicated for unit tests which could be expensive. The results of the Test-driven Development research conducted by Kollanus state that 16 out of 22 studies suggest that Test-driven Development increases external code quality. Most of the studies report that Test-driven Development does not affect internal code quality and as stated previously, the majority of the studies found that Test-driven Development decreases productivity (Kollanus, 2010). That means there is no correct answer whether Test-driven Development is superior to other practices or not. It all depends on the expertise of developers in creating tests, the project, its size, and its difficulty, making it relatively situational. It could be argued that for smaller projects or easy tasks, using Test-driven Development approach is useless since it only increases the time taken to develop the functioning code. However, it is a very good idea to make use of Test-driven Development for projects that are bigger in size and where the possibility of various unnoticed errors or bugs increases. This approach will make the software more bulletproof and might even increase the productivity of the developers that work on those projects. Task 2: Applying Test-driven Development Introduction For task 2 of the assignment the decision to implement a Net Income Calculator using Haskell has been made using Test-driven Development, as specified in the brief of the assignment. HUnit library has been utilized to create automated unit tests and QuickCheck has been utilized to develop property-based tests. 5 To develop the Net Income Calculator, the Test-driven Development cycles that were mentioned in the previous chapter will be closely followed during the development process. Creating Automated Unit Tests For every function, three tests with different values were created to test whether the output of the function is correct. This has been achieved using assertEqual, which is an assertion that expects a value depending on the value that is being passed in. The figure below shows an example of how it was implemented in this solution. Figure 1 - Unit Test Example Since there are multiple tests, the tests have been named to the function that is being tested accordingly. Additionally, a function was created that formats the tests into a more readable format. This solution has been introduced by Dr Neil Sculthrope in Advanced Software Engineering lecture 16. The figure below shows how it was achieved. Figure 2 - Test Formatting Every test was added into a list to make it easier to run from within the do block. And finally, the tests have been run to ensure that each one of them works. Since there were no functions implemented at the time, all of the tests have failed, indicating that the tests in fact work. 6 Developing the Algorithms After setting up the tests with values and correct results, the algorithms were developed. Instead of using Ints as the data type the decision was made to use Double data type to be able to output pennies as well. That means there was a requirement to somehow round up the two last decimal points and output those instead of a full double value. To achieve this, simply adding *1e2/1e2 after every output seemed to solve the issue, however, resulted in a slight problem. If the ending of the decimals were equal or smaller than 5, it would not round the number up correctly for an unknown reason. The figure below portrays this problem. Figure 3 - <=.005 Output using *1e2/1e2 To combat this, instead of using *1e2/1e2, a function was created that utilized floor to return the two decimal points that were required along with the value itself. However, it does not round up the decimal points itself, meaning that the Net Income Calculator might have an error of calculation of 1 penny, which was deemed insignificant to the developer of the Net Income Calculator. Figure 4 - decimalPoint Function According to the test cases, the functions were created that would comply with the values that are being expected from them by the tests. Two more functions were 7 implemented that would calculate the income after taxes with and without student income, by running the income value through every function and deducting the results from the given income value. After every new created function, the tests have been run to ensure that the code does not break, and all prior tests pass successfully. There was no need to refactor the code or improve the code quality, as Kent Beck in his book mentions being the last cycle (Beck, 2003), for the Net Income Calculator, since it is a relatively small project and every function requires to have specific math to calculate the tax thresholds and different tax rates, meaning there wasn’t much space left for improving the code quality anyway. A sample of one of the functions is portrayed in the figure below. Figure 5 - incomeTax function Bonus – Property-Based Tests The property-based tests do not seem to be useful for this project, however, the tests were implemented anyway to portray knowledge. The property-based tests select a hundred random Double values, however, as stated previously, they are not that useful because the values have negative values, or values with a lot of decimal points in them and do not replicate the income values very well. To do this, the functions were defined into properties and then tested by invoking quickCheck. Additionally, a single verboseCheck has been invoked to portray why property-based tests do not seem to be useful and what values are being passed into the functions for testing. 8 Figure 6 - Output of the Tests Task 3: Reflecting on Experiences According to the implementation process of the Net Income Calculator, the experience of using Test-driven Development was good overall. Creating tests first has definitely made implementation of each deduction function easier. Arguably if the task was to utilize the standard Test-last approach, it would have cost the developer more time to develop the Net Income Calculator for one simple reason. The functions mainly consist of pure math, therefore the chance of slight hiccups in the code increases, meaning that the developer would have to create tests after finishing the development process and then go through the code again, doubling the amount taken for the implementation process. Test-driven Development helped the developer get rid of this issue by having the tests in place first, with the only requirement being to create functions that would cater to the tests, simplifying the whole process. Both the C++ BST implementation and the Net Income Calculator implementations were developed using Test-driven Development using HUnit and QuickCheck, which helped with understanding the requirements of the functions. The developer would without a doubt utilize Test-driven Development again if there was a need for one of the implementations. However, Net Income Calculator is a very rare exception where the developer would use Test-driven Development. That is because it is not easy to design 9 the tests before the implementation is done, therefore, most of the implementations that are relatively small and easy to implement should not utilize Test-driven Development, since it would only waste the time of the developer by making the developer create meaningless tests. As mentioned previously, Net Income Calculator is one of the rare exceptions, because even though the implementation was relatively easy, it is mathheavy and requires high precision for it to function properly. For this reason, functional programming seemed to be a very good programming paradigm for this project, as it is based on the concept of many recursive mathematical functions instead of loops, which made the development process efficient and the code bug-free and made the experience enjoyable overall. In terms of property-based testing, this project was the first time the developer has utilized this concept and it did not seem too useful for the Net Income Calculator. Therefore, critically evaluating property-based testing is out of scope in this instance. However, it is evident that it can be very useful when creating tests for other implementations. To sum up, the developer will utilize Test-driven Development in the future for big projects, or for projects that require high precision. It is a very suitable practice for the industry since Test-driven Development promotes high-quality code in addition to providing the tests prior to the code, aside from other benefits. However, for smaller projects, the developer will stick with Test-last Development, as it only requires additional work for something that is not entirely worth it, or for something that does not take long to test manually after the implementation is complete. References Bhat, T. and Nagappan, N., 2006. Evaluating the efficacy of test-driven development. [online] ACM Digital Library. Available at: <https://dl.acm.org/doi/abs/10.1145/1159733.1159787> [Accessed 2 May 2022]. 10 Agarwal, N. and Deep, P., 2014. Obtaining better software product by using test first programming technique. [online] Ieeexplore.ieee.org. Available at: <https://ieeexplore.ieee.org/document/6949233> [Accessed 2 May 2022]. Beck, K., 2003. Test-driven Development: By Example. Shull, F., Melnik, G., Turhan, B., Layman, L., Diep, M. and Erdogmus, H., 2010. What Do We Know about Test-Driven Development?. [online] Ieeexplore.ieee.org. Available at: <https://ieeexplore.ieee.org/abstract/document/5604358> [Accessed 2 May 2022]. Janzen, D. and Saiedian, H., 2006. On the Influence of Test-Driven Development on Software Design. [online] Ieeexplore.ieee.org. Available at: <https://ieeexplore.ieee.org/abstract/document/1617340> [Accessed 2 May 2022]. Kollanus, S., 2010. Test-Driven Development - Still a Promising Approach?. [online] Ieeexplore.ieee.org. Available at: <https://ieeexplore.ieee.org/abstract/document/5655657> [Accessed 2 May 2022]. 11