Lab 4 - Development, Phase I
The development environment is up and running. During the previous homework you got to know gradle, worked with javafx and explored the course project. We will continue to extend the project with a special attention on one of the main development tools - debugger.
As you did in the previous homework you should again keep your Bitbucket issue tracker up to date. Close issues that are finished and add new issues if necessary. You should also link all your commits to issues in the issue tracker.
In this Lab, we also introduce Unit Testing and Refactoring. Since you have to start with the development in the third task of this Lab, it is important to keep in mind the testing and refactoring related activities. The following demo will help you to understand these activities.
Unit Testing and Refactoring
(for your own examination - not for submission)
Testing has also to be considered during the software development for ensuring the quality of the product. It is important developing the new functionality as well as testing it. In addition, code should be constantly maintained (refactored) to avoid design decay over time with more and more functionality getting integrated into the system. In Lab 6, you will be asked to show your unit tests as well as get some refactoring tasks to complete. You will make your life easier, if you start developing unit tests in parallel to your implementation of functionality and, in addition, do refactoring every once in a while. The following sub-sections will give you introductory examples on how to do unit testing and refactoring.
Test Automation and JUnit
In Lab 6, you will have to show unit tests for your code. You may have a look at the required test cases in Lab 6. For this reason, it is highly recommended to begin to write the unit tests in advance in both Lab 4 and Lab 5. It will make your team better prepared for completing your tasks properly. There is still a chance to add unit tests in Lab 6, though.
You can put test automation into practice using this code base 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 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.
- Add the following line
reports.junitXml.destination = "shippable/testresults"
to thetest
block in thebuild.gradle
file in order to allow Shippable to show the test reports.
apply plugin: 'java' repositories { mavenCentral() } dependencies { testCompile group: 'junit', name: 'junit', version: '4.+' } test { reports.junitXml.destination = "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: - oraclejdk8 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.
Your Tasks (must be submitted)
Task 1. The debugger (1p)
In general, debugger is a software that allows to see how the code is executed line-by-line with an ability to pause the execution on an arbitrary line. Debuggers are mostly used to analyze the algorithms and to find bugs.
Java Virtual Machine (JVM) allows to attach different debuggers via a specific protocol. During this homework you will use the built-in debugger included in your favorite JAVA IDE. In order to activate debugging in the JVM, you need to run your application with a specific flag. We will add this flag in our gradle configuration. Add the following lines to the build.gradle files under the sub-projects and run one of the projects.
run{ jvmArgs = ['-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=1044'] }
After the flag is set (when executed, must print in console something similar to "Listening for transport dt_socket at address: 1044"), you can attach the built-in debugger.
- If you are using Eclipse, choose: Run > Debug configurations..., there Remote Java Application and fill in the correct data (host = localhost, port = 1044 or other, depends on what you used in the build.gradle). When data is set, press "Apply" and then "Debug". Preferably, also open the debug perspective (Window > Open Perspective > Debug). If everything is OK, you will see that debugger is connected to the JVM. You will also see different active threads in the JVM.
- If you are using IntelliJ IDEA, choose: Run > Debug..., then Edit Configurations.... In the left panel, choose "Remote" and add a new configuration using the plus button. In this configuration, set host = localhost and port = 1044. When data is set, press "Apply" and then "Debug". If everything is OK, you will see that debugger is connected to the JVM.
The work with the debugger usually starts with setting the breakpoints on the code lines of interest. After breakpoints are set, the application is intentionally directed to trigger them. When JVM reaches the line with a breakpoint, it pauses the execution and allows developer to inspect and change the state.
Get familiar and practice the debugging process and answer the following questions:
Some of them can be answered also without the use of a debugger. However, try to think how the use of the debugger can help you in all the cases. Use the initial code given in the beginning of this homework (plus your homework 3 results). Extended code can give a bit different results that can be interpreted as false without an extensive check.
- Name at least one method, where a breakpoint will stop the application execution right after pressing the "New purchase" button.
- Name a method, where a breakpoint will stop the application execution right after pressing the "Confirm" button (during confirming the order).
- How many buttons are initialized when the application is started? You can add a breakpoint to Button.initialize().
- How many times the constructor new String() (without argument) in the class java.lang.String is used after pressing the New purchase button.
- Which constructors of javafx.scene.control.TextField are used?
You should deliver your task 1 results using the Bitbucket wiki.
Task 2. Logging task (1.5p)
So far you have used System.out.println
to print information about the program run to the console. This is not the best approach when we wish to output different levels of information depending on the program run. For example, while debugging we would like to output more information than during deployment. To achieve different levels of output you will be using Log4j, a java logging framework.
Log4j has 7 different levels:
- Off
- Fatal
- Error
- Warn
- Info
- Debug
- Trace
When logging something you can define the log level by calling the corresponding method (fatal
, error
, log
, etc.). In the Log4j settings file (log4j2.xml) you can define the level of output. If you choose error, messages of level error and fatal will be printed. If you choose debug, all levels above and including debug are printed and so on.
In your homework you are supposed to use three of these levels: Debug, Info and Error.
- Error: log runtime errors and other unexpected conditions. Tip: use entering a non numeric input for quantity is not an unexpected condition. This is something that the program should expect to happen. An unexpected condition could be for example a missing settings file or malfunction of the database.
- Info: log interesting events such as program startup and shutdown. It should only log very important events.
- Debug: log the flow through the system. Debug log can contain low level details, for example values entered by the user, method parameter values, executed if-statement branches etc. This can be useful for debugging program crashes and anomalies when attaching a real debugger is not possible.
The build.gradle file contains the following lines that download and add log4j to the project.
compile group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.6.2' compile group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.6.2'
The Log4j settings file log4j2.xml is located in the resources folder and currently sets the log level to info. You can change this in the xml file.
Look into SalesSystemUI to see how to use the Log4j logger. Currently it logs the javafx version number. Add the logger to other classes and log additional important information at appropriate levels.
Logger should be used at least in the following classes:
- SaleSystemGUI project:
- SalesSystemUI
- HistoryController
- PurchaseController
- StockController
- TeamController
- SaleSystemCLI project
- ConsoleUI
- Main project:
- ShoppingCart
If you add new functionalities, then these should also be logged if applicable.
At the time of your submission, messages of all 3 levels (debug, info, error) should be visible in the command line.
Note: logging should make your life easier. Especially when debugging the application.
Task 3. Extending the POS System (7)
Implement all the functional requirements except for database support. Keep the issue tracker up to date! Refer to issues in all commits.
You also need to think about exceptions. Your POS application should not show or accept incorrect data (includes rounding).
Note: Despite the short instructions this is the biggest task.
While developing the application you should consider the following basic principles for well written code:
- DAO classes should not contain any business logic. DAO classes should only save and retrieve objects. This also makes writing tests easier.
- GUI and CLI should not contain any business logic. Everything that is done in both GUI and CLI projects, has to be written in the common project. Code should not be repeated (create a reusable methods instead of copy-pasting code).
- Every class follows the single responsibility principle (SRP): a class handles only one thing and does not have to know how other classes solve problems. Look at the public methods of each of your classes and try to describe the purpose of that class. If the description contains "and", then probably the class is violating SRP.
- Names of classes, methods and variable have to correspond to their content. Code comments don't substitute good variable/field/method names. Don't be afraid of long and descriptive names - they are much more useful than short and obscure names. If naming a method is difficult, then maybe the method is doing too many different things. Apply SRP and break it into smaller independant methods which are easier to name.
- GUI has to be defined in FXML. GUI classes should only include event handling.
Submission of results
Your results must be submitted using your Bitbucket repository and the Bitbucket wiki. Create a tag named "homework-4" and add a link to the labs wiki page, this will count as a submission (0.5p).
NOTE: In the assessment/consultation lab you must be able to show the solution of task 1 and partial solutions of tasks 2 and 3.