Workshops
We use the ‘Waglys’ system for getting help during the workshop. So if you are in need of assistance please add yourself to the queue using Waglys.
If you find yourself in a situation where the tools do not work as you expect or as described by the tutorial, you should try to figure out if it can be solved in a simple way, and not just wait. If you can’t work around it in a reasonable amount of time, say a couple of minutes, then just continue with the next step. Of course, ask for help if you get stuck. There are so many different versions and platforms, which makes it very hard to give a description that works in every thinkable context. Later in your professional life you will find yourself in similar situations, more often than you think. So, see it as a learning opportunity and provide feedback to us.
Build automation: Maven
This workshop introduces the build automation tool Maven. After this workshop you should continue with the Git workshop. You should by then be ready to set up your project development environment. If using your own computer be sure to have Java 8 installed.
Introducing Maven
If you use your own computer you need to install Maven. We start with using Maven from the command-line (a shell/terminal), such that you learn about the tool’s details and possibilities. Later you will probably use the tool from within an IDE. Linux and Mac already have decent terminals, but the standard Windows shell is a bit primitive. It is possible to install an Linux subsystem on recent Windows 10 versions.
After you have installed Maven (which is pre-installed on Chalmers Linux lab computers), start with this tutorial: Your First Maven Project.
Next, download the project BasicSwing and take the following steps:
-
Inspect the project in particular the file
pom.xml
-
Execute:
mvn compile
in same directory as where the filepom.xml
resides. Thepom.xml
configuration file contains a number of dependencies, such as junit and findbugs. These will be automatically downloaded from from a central repository). Inspect the project directories; run thetree
command on the command-line, this gives a nice overview of the directory structure and files. - Try to run the project from the command-line.
-
Execute
mvn clean
and inspect the result of this command; run for example thetree
command again. -
Execute
mvn package
and inspect really everything. Pay particularly attention to the generated HTML-pages in the subdirectories oftarget/site
. -
The previous step created an executable jar-file, try to run it from the command-line. Try max 10 min, if you don’t get it to work, just continue with the next task. (hint: make clear in the
pom.xml
file what the main class file should be, try to find a solution, ‘googla’)
Adding a dependency
One of the main reasons to use Maven is its dependency handling capabilities. It is often wise to try to find a suitable library that offers the functionality that you want (don’t reinvent the wheel).
-
Create another new example project or use the
basicSwing
example project. -
Create a class
Monster
with some instance variables (for some game, does not matter). This time we want to reduce the amount of boilerplate code and instead generate the straightforward code. Lets to use Lombok for this (inspect its features). -
Add a dependency to the Lombok library into the
pom.xml
. -
Using your favorite IDE, code the
Monster
class and use the Lombok annotations to reduce the boilerplate code. Use your imagination and as much as possible from Lombok, such as: setter/getter/equals/hashCode/constructor. - Use Maven (possibly from inside some IDE) to compile and package the program (Lombok should be downloaded). Try to run the (useless) program to see if it works. If using an IDE, the generated setters/getters will show up in code completion!
- Inspect the generated jar file, is Lombok packaged with it?
-
Inspect your local Maven repository, the
.m2
directory. Locate Lombok in it.
Version control: Git
We start this tutorial with reading the following article:
https://git-scm.com/book/en/v2/Getting-Started-Git-Basics
Try to grasp the basic ideas behind version control and git and go back later to this documentation if you don’t understand something.
Git workflow
Branches are essential to the Git workflow. So, we continue by reading a about Git branches before we explain the workflow and start exercising it.
When using Git we need an ordered process, a workflow. Our workflow, inspired by this article, uses three branches:
- The remote branch “master” (default name), also known as origin/master. Created when remote repo is created and exists during the entire lifetime of the repository.
- The local branch master. The local copy of the remote master, which is created when a project is cloned from the remote repo. Could be up to date with origin/master or not. Always present.
- A local branch named as “the task to be accomplished”. This branch is created from the local master for each task. This branch is strictly private for the contributor. When task is fulfilled the branch is merged into local master (the remote master is subsequently updated with the changes in the local master). Finally the branch is deleted.
Workflow steps
Assume we have a remote repo and have cloned it locally. We now start working with the local clone.
-
git checkout master
You are probably already on local master -
git pull
(=git pull origin master
) To update local master -
git checkout -b myTask
Create and switch to branchmyTask
-
Optional: Use
git branch -a
to show all branches and git status to see what branch you’re on. - Do some coding (some well defined (sub) task), after max 30 min. and then go to 6.
-
git commit -a -m “…a sensible message…”
Commit on branchmyTask
- Goto 5. until task finished (max 2 hours before next point)
- Ok, assume task finished. Project should be executable, all test should pass.
-
Now we start to integrate our changes into the project (Tip: if you are new to this, make a copy (zip) of the current code)
-
git checkout master
-
git pull
-
git checkout myTask
-
git rebase master
-
git checkout master
-
git merge myTask
-
git push
(=git push origin master
) Push to remote repo -
git branch -d myTask
Delete branch
-
-
Now everybody should be able to see your contribution (i.e.
git pull
). - Continue with next task, go to 2.
Exercising the workflow
To exercise the workflow (locally) do the following
-
We are going to build a repository and two working copies. Select a directory to store them, lets say directory
tmp
. -
We will simulate a remote repo on your local machine. Create a directory
oopp-git
intmp
. -
Go into the created directory and execute
git –bare init
, this creates an empty Git repository. -
Go back to
tmp
. Clone the remote repo twice (will simulate two different users local repos):git clone oopp-git oopp-a-work git clone oopp-git oopp-b-work
-
There should now be three directories in
tmp
:oopp-git
,oopp-a-work
andoopp-b-work
. -
Create a Java-file with some simple code in
oopp-a-work
, then in same directory execute:git status (just to check) git add . (must add new files) git status git commit -a -m init git status git push
Let
oopp-b-work
executegit pull
. Both should now have the same code. Usegitk –all
(for botha
andb
) // Or other graphical tool -
The clones (working copies) represent two different users. Open one terminal for
oopp-a-work
and one foroopp-b-work
. Let users work on the code using the workflow. Use commit message to identify the changes. Usegitk –all
(or similar) for both users after each step in the workflow.All contributions should appear in a linear fashion when inspecting in
gitk
. If not, you messed up. Remove everything and start from 1.If getting a conflict, see and also below.
Merge conflicts
Now we’ll create a conflict and then resolve it. Continue with the Git-repos from above. We assume both users have the same and latest versions of the code and uses the workflow.
-
Let user
a
modify a code row (note which one), commit and push it to origin (a full workflow cycle). -
Let user
b
modify same row and commit, later in workflow when … -
…
b
pulls changes from the remote repo, a conflict message should show up. -
Open an editor in
oopp-b-work
and resolve the conflict (edit the code, there are markings like<<<<<<<
and more, decide what to keep) then executegit add
. to mark as resolved. -
Execute
git commit -m “resolved conflict”
in same directory and then push the changes:git push
. -
Now pull the changes in
a
and check if the conflict has been resolved.
Setting up GitHub
We prefer GitHub as our remote repository for the project (if you own a server it’s also possible to host the remote repository on the server).
- All group members must create an account on GitHub (note name and password!).
-
All should read and do step 1, Setup Git, read on GitHub Bootcamp. To check use:
git config -l
-
Select one group member as repository owner. Let the owner create a repository, follow step 2 on Bootcamp (use a good name!). Choose to create an initial
.gitgnore file
for Java and a README-file. WARNING:.gitignore
should be adapted to your IDE (not pushing IDE specific files to the repo).. -
Add collaborators for the repository (collaborators must have an account on GitHub).
Clone a repository
Let (remote) repository owner do the following:
- Select a local directory where you would like to have your local repository.
-
Step into directory and execute (copy your URL from GitHub, last part is name of local directory for the local repository):
git clone <url of your repo>
-
Inspect directory for the local repo! Files from remote should be in the local repository. There should be a hidden
.git
-directory and a.gitignore-file
, check! -
Execute:
git remote -v
to check that the remote is ok. Should list (like):origin <your url> (fetch)
origin <your url> (push)
-
Copy the content of the Maven project template to the local repository directory. Execute:
git status
. Should look like (delete directorytarget
if present, this should be an entry in your.gitignore
file):Untracked files: (use "git add <file>..." to include in what will be committed) findbugs-exclude.xml pmd-rules.xml pom.xml src/
-
Rename the project content to your taste (name, artifactId … in
pom.xml
, packages insrc
, etc). Open project in your IDE and refactor/inspect (this is easily accomplished in IntelliJ). Make it really beautiful! -
When satisfied, add and commit everything to the local repo, execute
git add . (yes, dot last) git status (a list of “new” files should appear) git commit -a -m initial git status (...ahead of 'origin/master' by 1 commit) git log
-
Now push the local repo content (project) to the remote repo, execute:
git push
and thengit status
- Go to GitHub (possibly refresh page). Magic (hopefully)!
- Check GitHub contributors (should be one and only one, the repo owner).
-
Let everyone clone the repo and start working using the workflow introduced earlier.
Pair programming
If working in pairs you have to share the commits, commit from different machines and/or use the –author
option when committing.
If you don't distribute commits evenly you will fail the course because of too
little contribution to the project!
Unit testing: JUnit
In this exercise we will exercise how to write unit tests. An unit test is running a piece of code and observe if the expected output (or side-effect) is as we expected. So for an unit test we need to specify what software to run and its expected output. We will use the testing framework JUnit for specifying unit tests and cover its basic functionality, that is, how to write simple unit tests. Most IDEs have built-in support for JUnit. We recommend you use IntelliJ, which is pre-installed on the lab computers, but you may use an IDE of your choice.
Preparation
-
Download the example project
ws_junit.zip
(a Maven project and open it with IntelliJ (or import it into the IDE of choice). -
We will work with a number of predefined classes:
List
andNode
(our tests target the main classList
). If you don’t know what a linked list is, please read the explanation first before continuing. -
The next step is to inspect the code. Take special note of the
toString()
method in theNode
class. This is a good method to use during debugging and development, and testing of theList
class.
Writing tests
We with writing some JUnit tests.
Step 1.
In class TestList
edit the testAdd()
method such that it looks like the following snippet of code (you possibly need to fix some imports):
@Test
public void testAdd() {
List l = new List();
l.add(1); // Call method to test
assertTrue(l.getLength() == 1); // The logical check
}
The last row in the method is called an assertion. If the expression evaluates to true
, it means that the unit test was successful. If, on the other hand, the assertion fails, an error is reported. Run the test! It should pass.
This is how units tests work, it either fails or succeeds. It may be tempting to print values of variables to stdout
using System.out.println()
, but such statements should not be part of an unit test. Such print statements are maybe useful for debugging, but should be removed when the test is completed.
The testAdd()
method tests a method with return type void
. This means that there is no return value that can be checked. Consequently, there needs to be some other way to observer if the method operates as we expect (in this case by inspecting the length of the list using the getLength()
method). If there is no method available to check, a method may be added to the class in order for it to be testable.
Step 2
Checkout the following articles:
- JUnit annotation examples
- Unit test assertions, stop reading at Hamcrest
Step 3
You can invoke a failure by modifying the assertion in the testAdd()
method to something that should fail. Alter the assertion such that it fails and run the test again. Inspect the error and output on the terminal. (then restore!)
Step 4
Next, let’s write a test for the remove()
method of the List
class. The method removes the first node in the list and returns the contents of that node. Make use of the return value when you write the test. The test should have at least two assertions. If test fails, use the debugger to debug and fix the method. Checkout this video about how to use the debugger in IntelliJ.
Step 5
You should now write a test for the get()
method. You should do the following operations in the test:
- Add five values to the list
- Return (and check) the value for index 2 in the list
Something is wrong with the get()
method and it does not work correctly. Use the debugger in order to try to find out what’s wrong.
Step 6
It is also important that we test that methods behave correctly (and report an error) when they get invalid data. The get()
method of the List
class actually has such an error check. To test it we can create the following test:
@Test(expected=IllegalArgumentException.class)
public void testGetBadIndex() {
// Get a list then ...
list.get(-1); // Exception!!!
}
The test checks that the expected exception occurs when we request an index that doesn’t exist. We specify this behaviour by modifying the @Test
annotation. Implement the test and run it.
Step 7
Write one (or preferably more) test for the copy
method in class List
.
Step 8: Fixtures
Many tests need some kind of initialization before they can run. In certain cases, a test method might also need to run some shutdown code in order to free up resources that were needed during the test. Operations such as these can be done inside special methods that are executed by JUnit in preparation of each test. These are called fixtures.
To try out this feature, add the code below to the TestList
class. Note that the printouts below were added just for demonstration purposes and should never be part of a JUnit test class under normal conditions.
@BeforeClass
public static void beforeClass(){ //First of all
System.out.println("Before class");
}
@AfterClass
public static void afterClass(){ // Last of all
System.out.println("After class");
}
@Before
public void before(){ //Before each test method
System.out.println("Before");
}
@After
public void after(){ //After each test method
System.out.println("After");
}
Continuous integration: Travis
Continuous Integration (CI) is the process of automatically building and running tests whenever a change is committed. We are going to use Travis CI service, which is free for open-source projects hosted on GitHub. Travis adds so-called webhooks to your GitHub project and starts building and testing your project automatically after you push a commit to GitHub.
Adding git
To place the List project, you have been using in the JUnit tutorial, under Git version control, execute the following commands in the terminal in the root directory of the List project:
mvn clean
git init
git add .
git commit -m "Initial commit"
The first command removes generated files (class files etc.), and the three git commands create a repository and add all existing files to it.
GitHub repository
Create a new empty repository on GitHub, that is, without a README and .gitignore file. Copy the URL of the newly create repository and run the following commands in the terminal:
git remote add origin <URL of repository>
git push -u origin master
Now you have added the GitHub repository as a remote repository, which you can check with git remote -v
, and pushed the contents of the project to GitHub. Please check this on GitHub if your files appear in the GitHub repo.
An overview of the steps you took can be read here.
Adding Travis CI
Now we are going to grant Travis access to our GitHub repository, such that Travis can build and test our project.
- Go to http://travis-ci.org and choose ‘Sign in with GitHub’ on the top of the page to the right.
- Accept the authorization; you’ll be redirected to GitHub.
- Click the green Activate button and enable Travis for the new repository.
- Click on the repository, you be redirected to a webpage where you can follow the active builds for you repo. Keep this window open.
-
Create a file in the root of the project directory with the filename
.travis.yml
and add the following line to it
save it and place it under version control withlanguage: java
git add .travis.yml
. -
Record the changes
The way you get notified by Travis is configurable. You can for example integrate it with Slack.git commit -m “Added travis yaml file”
and push to GitHub:git push
. Keep a close watch on the browser window, Travis should be fetching, building and testing! If all went well, you will (probably) get an e-mail with the message that the build succeeded. Wohooo! -
Inspect the log on Travis.
We have integrated Travis CI into our project and any subsequent commit will trigger a build.
Refactoring
Now that we have Continuous Integration in place and all tests are passing, it is time to refactor our code. The List
class is hard coded to store values of Integer
type. Generalise the code such that we can store values of an arbitrary type in the list, that is, make it generic.
Update the tests to the new interface and run the tests again to make sure you haven’t broken something. Make sure to commit and push you changes.
Deep copy
Note: a more difficult exercise
The copy
method makes a shallow copy of a list, which can lead to alias problems. Write a test that displays such a alias problem. Again, commit and push your changes, this time Travis should inform you that the build/test has failed.
Alter the copy()
in the List
class, such that it makes a deep copy of the list. Again: commit and push!