Unit Testing, Continuous Integration, and Refactoring
(for your own examination - not for submission)
The following sections will give you introductory examples on:
Test Automation and JUnit
You can put test automation into practice using this codebase for a demo application: unit-testing-demoapp.zip.
The project has three Java model classes (none of the classes has the main
method because we are not dealing with the independently executable application). The project consists of only the following model classes:
SaleItem
- Can be used for different products. Each product has a name and unit price.Bill
- One bill record. Consists of one more rows. Can calculate the total sum of the rows.BillRow
- One row in the bill. Has a link to the corresponding product (SaleItem) and sold quantity number. Can have also discount percentage for the concrete sale and product (by default discount percentage is 0%). Can calculate row total price.
Application code is located in the \src\main\java
folder. In addition, the project includes also the automatic unit tests created using the JUnit framework. The code of the tests is located in the \src\test\java
folder. Tests can be executed with the gradle task test
or the IDE built-in tools.
For training: Test classes have many empty test methods that should check different model properties and functionality. Try to develop these tests.
Note! One of the classes has several mistakes in error handling. Can you find them with tests?
Continuous Integration (CI)
While developing your application you continuously write code and test it. It is assumed that you re-run your test suits every time the new change is introduced. You, as a good developer, may consider pressing the same button over and over again as a tedious work, and it definitely should be automated. Such a technique exists and called Continuous Integration (CI). Also, CI helps to overcome the "it has been working on my machine ¯\_(ツ)_/¯" problem. After each push to the repository, CI automatically build and test your application and notify you of the status.
We will use cloud-based CI service called Shippable. To set it up:
- First of all initialize git, create bitbucket repository and push unit-testing-demoapp project as described in Lab3 instructions.
- Update the main
build.gradle
file in order to allow Shippable to show the test reports. The file should look like the following:
apply plugin: 'java' version = '1.0' repositories { mavenCentral() } dependencies { compile("org.yaml:snakeyaml:1.23") testCompile("junit:junit:4.+") } test { reports { junitXml.enabled = true html.enabled = false } testResultsDirName = file("shippable/testresults") testReportDirName = file("shippable/testresults") testLogging { events "passed", "skipped", "failed" } }
- Sign in to Shippable with your Bitbucket account.
- Select Bitbucket icon in the left-hand menu and then choose your bitbucket username.
- If you do not see your project in the list, then click on "plus" sign in the top-right corner
- Enable your current test project
- Create a file named
shippable.yml
in the root of your repository with the following content:
language: java jdk: - oraclejdk11 build: ci: - gradle assemble - gradle test
- Commit your changes
git add . git commit – m "shippable set up" git push
And that is it. After each git push
your project's build will be triggered automatically on Shippable and the build status will be displayed on Bitbucket > Commits view > Builds column
Refactoring
The code base for the demo application is here: refactoring-demoapp.zip.
The application is reading data about doctors and patients from the CSV files located in the resources folder. After reading the files, the application creates corresponding objects and prints them to the console. However, the application code is far from perfection and has many problems, especially related to the code duplication. You need to refactor the code and make it better. Hint: You can use inheritance/generalization and split/combine methods. Also, obvious bugs in exception handling should be fixed - only catch exceptions you can reasonable handle (printStackTrace or logging is almost never enough) and close streams in finally blocks.