Workshops

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:

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).

  1. Create another new example project or use the basicSwing example project.
  2. 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).
  3. Add a dependency to the Lombok library into the pom.xml.
  4. 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.
  5. 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!
  6. Inspect the generated jar file, is Lombok packaged with it?
  7. 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:

Workflow steps

Assume we have a remote repo and have cloned it locally. We now start working with the local clone.

  1. git checkout master You are probably already on local master
  2. git pull (= git pull origin master) To update local master
  3. git checkout -b myTask Create and switch to branch myTask
  4. Optional: Use git branch -a to show all branches and git status to see what branch you’re on.
  5. Do some coding (some well defined (sub) task), after max 30 min. and then go to 6.
  6. git commit -a -m “…a sensible message…” Commit on branch myTask
  7. Goto 5. until task finished (max 2 hours before next point)
  8. Ok, assume task finished. Project should be executable, all test should pass.
  9. 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)
    1. git checkout master
    2. git pull
    3. git checkout myTask
    4. git rebase master
    5. git checkout master
    6. git merge myTask
    7. git push (= git push origin master) Push to remote repo
    8. git branch -d myTask Delete branch
  10. Now everybody should be able to see your contribution (i.e. git pull).
  11. Continue with next task, go to 2.

Exercising the workflow

To exercise the workflow (locally) do the following

  1. We are going to build a repository and two working copies. Select a directory to store them, lets say directory tmp.
  2. We will simulate a remote repo on your local machine. Create a directory oopp-git in tmp.
  3. Go into the created directory and execute git –bare init, this creates an empty Git repository.
  4. 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
  5. There should now be three directories in tmp: oopp-git, oopp-a-work and oopp-b-work.
  6. 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 execute git pull. Both should now have the same code. Use

    gitk –all (for both a and b) // Or other graphical tool
  7. The clones (working copies) represent two different users. Open one terminal for oopp-a-work and one for oopp-b-work. Let users work on the code using the workflow. Use commit message to identify the changes. Use gitk –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.

  1. Let user a modify a code row (note which one), commit and push it to origin (a full workflow cycle).
  2. Let user b modify same row and commit, later in workflow when …
  3. b pulls changes from the remote repo, a conflict message should show up.
  4. 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 execute git add. to mark as resolved.
  5. Execute git commit -m “resolved conflict” in same directory and then push the changes: git push.
  6. 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).

  1. All group members must create an account on GitHub (note name and password!).
  2. All should read and do step 1, Setup Git, read on GitHub Bootcamp. To check use:

    git config -l

    Also to see how to cache password (don’t need to login for every push)
  3. 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)..
  4. Add collaborators for the repository (collaborators must have an account on GitHub).

Clone a repository

Let (remote) repository owner do the following:

  1. Select a local directory where you would like to have your local repository.
  2. 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>
  3. 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!
  4. Execute: git remote -v to check that the remote is ok. Should list (like):

    origin <your url> (fetch) origin <your url> (push)
  5. Copy the content of the Maven project template to the local repository directory. Execute: git status. Should look like (delete directory target 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/
  6. Rename the project content to your taste (name, artifactId … in pom.xml, packages in src, etc). Open project in your IDE and refactor/inspect (this is easily accomplished in IntelliJ). Make it really beautiful!
  7. 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
  8. Now push the local repo content (project) to the remote repo, execute: git push and then git status
  9. Go to GitHub (possibly refresh page). Magic (hopefully)!
  10. Check GitHub contributors (should be one and only one, the repo owner).
  11. 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

  1. Download the example project ws_junit.zip (a Maven project and open it with IntelliJ (or import it into the IDE of choice).
  2. We will work with a number of predefined classes: List and Node (our tests target the main class List). If you don’t know what a linked list is, please read the explanation first before continuing.
  3. The next step is to inspect the code. Take special note of the toString() method in the Node class. This is a good method to use during debugging and development, and testing of the List 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:

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:

  1. Add five values to the list
  2. 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.

  1. Go to http://travis-ci.org and choose ‘Sign in with GitHub’ on the top of the page to the right.
  2. Accept the authorization; you’ll be redirected to GitHub.
  3. Click the green Activate button and enable Travis for the new repository.
  4. Click on the repository, you be redirected to a webpage where you can follow the active builds for you repo. Keep this window open.
  5. Create a file in the root of the project directory with the filename .travis.yml and add the following line to it

    language: java
    save it and place it under version control with git add .travis.yml.
  6. Record the changes 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!

    The way you get notified by Travis is configurable. You can for example integrate it with Slack.
  7. 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!

Menu