Skip to main content

Using GitHub and Command Line to Test and Debug Code

For the following instructions, we will use Lab 7 repo as our example.

Clone Assigned Repo

From the terminal, you will clone your assigned repo.

Note: You should be in the directory where you would like to store the lab repo (e.g., csc116):

1
2
$ cd <PARENT_DIRECTORY>/csc116
$ git clone COPIED_URL

Git Overview

.gitignore in Repo

If your repo does not contain .gitignore, (a) copy .gitignore from another repo using cp command OR (b) create the file with the following contents:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# This rule ignores class files
*.class

# This rule ignores the contents of the bin/ build output directory (which includes .class files)
bin/

# This rule ignores temporary files that end with ~
*~

# This rule ignores the contents of the .settings directory that text editors will sometimes make
.settings/

# This rule ignores .DS_Store on Macs
**/.DS_Store

# This rule ignores VS Code directory
**/.vscode

Project Structure of Repo

Within your repo, you will use the following project structure where within your repo you have a project directory (such as Lab7 or Project1). Within the project directory, you will the following directories (which may be empty): bin (.class files), doc (HTML files), lib (library jar files), project_docs (project documents, such as system test plan), src (.java files), test (test programs), and test-files (input/output files for tests).

The following shows the structure for Lab 7:

1
2
3
4
5
6
7
8
9
10
11
- Repo (e.g., csc116-001-Lab7-70)
- - .gitignore
- - README.md
- - Lab7
- - - bin
- - - doc
- - - lib
- - - project_docs
- - - src
- - - test
- - - test-files

Given an empty repo directory, you can create the needed directories with the following commands:

1
2
3
4
5
$ pwd
<PARENT_DIRECTORY>/csc116/csc116-001-Lab7-70
$ mkdir Lab7
$ cd Lab7
$ mkdir bin doc lib project_docs src test test-files

JUnit Jar File

Download junit-platform-console-standalone-1.6.2.jar file into your lib directory in your project. The jar file can also be copied from other local repos you have on your machine.

Compile the Project Code

To compile our project, we have to do a series of steps in the terminal. We have to compile the source code, compile the test code, and also tell Java where to find the JUnit library file in our lib directory.

First, change directory into the top-level of your project (e.g., Lab7 directory).

Compile Source Code

Assuming you are currently in your top-level project directory (Lab7), then compile your source code using the following command:

1
javac -d bin -cp bin src/SecretMessage.java

Compile Test Code

Assuming you are currently in your top-level project directory (SecretMessage), then compile your test code using the following command on Linux/Mac:

1
javac -d bin -cp "bin:lib/*" test/SecretMessageTest.java

Compile your test code using the following command on Windows:

1
javac -d bin -cp "bin;lib/*" test/SecretMessageTest.java

Execute the Test Cases

Now that we have compiled all the source and test code, we can execute our test cases.

When we execute Java programs, we are actually executing the .class files.

To execute the SecretMessageTest test cases, make sure you are in your top-level project directory (Lab7) and use the following command:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
$ java -jar lib/junit-platform-console-standalone-1.6.2.jar -cp bin -c SecretMessageTest


Thanks for using JUnit! Support its development at https://junit.org/sponsoring

╷
├─ JUnit Jupiter ✔
│  └─ SecretMessageTest ✔
│     ├─ testDecryptHlloe() ✘ String index out of range: 5
│     ├─ testSwapCharacterEncryptHello() ✘ String index out of range: 5
│     ├─ testEncryptHello() ✘ String index out of range: 5
│     ├─ testMoveCharacterEncryptHello() ✘ Tests moveCharacter for string of length 5 ==> expected: <oelHl> but was: <lHHl>
│     ├─ testSwapSubstringsEncryptHello() ✘ Tests swapSubstrings if length is odd and not divisible by three ==> expected: <Hlloe> but was: <lHlloe>
│     └─ testMoveCharacterInvalid() ✔
└─ JUnit Vintage ✔

Failures (5):
  JUnit Jupiter:SecretMessageTest:testDecryptHlloe()
    MethodSource [className = 'SecretMessageTest', methodName = 'testDecryptHlloe', methodParameterTypes = '']
    => java.lang.StringIndexOutOfBoundsException: String index out of range: 5
       java.base/java.lang.StringLatin1.charAt(StringLatin1.java:48)
       java.base/java.lang.String.charAt(String.java:1515)
       SecretMessage.swapCharacter(SecretMessage.java:91)
       SecretMessage.decrypt(SecretMessage.java:73)
       SecretMessageTest.testDecryptHlloe(SecretMessageTest.java:71)
       java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
       java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
       java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
       java.base/java.lang.reflect.Method.invoke(Method.java:568)
       org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:686)
       [...]
  JUnit Jupiter:SecretMessageTest:testSwapCharacterEncryptHello()
    MethodSource [className = 'SecretMessageTest', methodName = 'testSwapCharacterEncryptHello', methodParameterTypes = '']
    => java.lang.StringIndexOutOfBoundsException: String index out of range: 5
       java.base/java.lang.StringLatin1.charAt(StringLatin1.java:48)
       java.base/java.lang.String.charAt(String.java:1515)
       SecretMessage.swapCharacter(SecretMessage.java:91)
       SecretMessageTest.testSwapCharacterEncryptHello(SecretMessageTest.java:19)
       java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
       java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
       java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
       java.base/java.lang.reflect.Method.invoke(Method.java:568)
       org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:686)
       org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60)
       [...]
  JUnit Jupiter:SecretMessageTest:testEncryptHello()
    MethodSource [className = 'SecretMessageTest', methodName = 'testEncryptHello', methodParameterTypes = '']
    => java.lang.StringIndexOutOfBoundsException: String index out of range: 5
       java.base/java.lang.StringLatin1.charAt(StringLatin1.java:48)
       java.base/java.lang.String.charAt(String.java:1515)
       SecretMessage.swapCharacter(SecretMessage.java:91)
       SecretMessage.encrypt(SecretMessage.java:54)
       SecretMessageTest.testEncryptHello(SecretMessageTest.java:58)
       java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
       java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
       java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
       java.base/java.lang.reflect.Method.invoke(Method.java:568)
       org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:686)
       [...]
  JUnit Jupiter:SecretMessageTest:testMoveCharacterEncryptHello()
    MethodSource [className = 'SecretMessageTest', methodName = 'testMoveCharacterEncryptHello', methodParameterTypes = '']
    => org.opentest4j.AssertionFailedError: Tests moveCharacter for string of length 5 ==> expected: <oelHl> but was: <lHHl>
       org.junit.jupiter.api.AssertionUtils.fail(AssertionUtils.java:55)
       org.junit.jupiter.api.AssertionUtils.failNotEqual(AssertionUtils.java:62)
       org.junit.jupiter.api.AssertEquals.assertEquals(AssertEquals.java:182)
       org.junit.jupiter.api.Assertions.assertEquals(Assertions.java:1135)
       SecretMessageTest.testMoveCharacterEncryptHello(SecretMessageTest.java:32)
       java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
       java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
       java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
       java.base/java.lang.reflect.Method.invoke(Method.java:568)
       org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:686)
       [...]
  JUnit Jupiter:SecretMessageTest:testSwapSubstringsEncryptHello()
    MethodSource [className = 'SecretMessageTest', methodName = 'testSwapSubstringsEncryptHello', methodParameterTypes = '']
    => org.opentest4j.AssertionFailedError: Tests swapSubstrings if length is odd and not divisible by three ==> expected: <Hlloe> but was: <lHlloe>
       org.junit.jupiter.api.AssertionUtils.fail(AssertionUtils.java:55)
       org.junit.jupiter.api.AssertionUtils.failNotEqual(AssertionUtils.java:62)
       org.junit.jupiter.api.AssertEquals.assertEquals(AssertEquals.java:182)
       org.junit.jupiter.api.Assertions.assertEquals(Assertions.java:1135)
       SecretMessageTest.testSwapSubstringsEncryptHello(SecretMessageTest.java:45)
       java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
       java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
       java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
       java.base/java.lang.reflect.Method.invoke(Method.java:568)
       org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:686)
       [...]

Test run finished after 42 ms
[         3 containers found      ]
[         0 containers skipped    ]
[         3 containers started    ]
[         0 containers aborted    ]
[         3 containers successful ]
[         0 containers failed     ]
[         6 tests found           ]
[         0 tests skipped         ]
[         6 tests started         ]
[         0 tests aborted         ]
[         1 tests successful      ]
[         5 tests failed          ]

Debugging Code Based On Failing Test Cases

We are currently failing 5 tests. We are going to focus on failing test cases for methods that do not call other SecretMessage methods.

testSwapCharacterEncryptHello

testSwapCharacterEncryptHello throws an StringIndexOutOfBoundsException:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
  JUnit Jupiter:SecretMessageTest:testSwapCharacterEncryptHello()
    MethodSource [className = 'SecretMessageTest', methodName = 'testSwapCharacterEncryptHello', methodParameterTypes = '']
    => java.lang.StringIndexOutOfBoundsException: String index out of range: 5
       java.base/java.lang.StringLatin1.charAt(StringLatin1.java:48)
       java.base/java.lang.String.charAt(String.java:1515)
       SecretMessage.swapCharacter(SecretMessage.java:91)
       SecretMessageTest.testSwapCharacterEncryptHello(SecretMessageTest.java:19)
       java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
       java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
       java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
       java.base/java.lang.reflect.Method.invoke(Method.java:568)
       org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:686)
       org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60)
       [...]

Within the testSwapCharacterEncryptHello stacktrace output, we will focus on the lines related to SecretMessageTest.java and SecretMessage.java:

1
2
       SecretMessage.swapCharacter(SecretMessage.java:91)
       SecretMessageTest.testSwapCharacterEncryptHello(SecretMessageTest.java:19)

First we examine line 19 of SecretMessageTest.java, which is a call to assertEquals:

1
2
3
4
5
6
7
8
9
    /**
     * Tests swapCharacter method - If first and last characters are letters, swap
     * first and last characters of message.
     */
    @Test
    public void testSwapCharacterEncryptHello() {
        assertEquals("oellH", SecretMessage.swapCharacter("Hello"),
                "Tests swapCharacter if first and last characters are letters");
    }

The call to assertEquals tells us the following about our test case:

  • Input: “Hello”
  • Method testing: SecretMessage.swapCharacter
  • Expected output: “oellH”
  • Actual output: SecretMessage.swapCharacter("Hello")

This information will help us as we trace the swapCharacter method.

Next we examine line 91 of SecretMessage.java: char last = message.charAt(length);, which throws StringIndexOutOfBoundsException stating String index out of range: 5

1
2
3
4
5
6
7
    public static String swapCharacter(String message) {
        int length = message.length();
        if (length < 2) {
            return message;
        }
        char first = message.charAt(0);
        char last = message.charAt(length);

Based on the test case, we know that message is “Hello”, which has a length of 5. Since indexing of strings starting at 0, the largest index for a string is length - 1. Therefore, we have found a bug (error) in SecretMessage.

After changing line 19 to char last = message.charAt(length - 1);, we recompile our code and test program, then execute our test program again to find that testSwapCharacterEncryptHello is now passing:

1
2
3
4
5
6
7
8
9
10
╷
├─ JUnit Jupiter ✔
│  └─ SecretMessageTest ✔
│     ├─ testDecryptHlloe() ✘ Tests decrypting Hlloe to Hello ==> expected: <Hello> but was: <Hlloe>
│     ├─ testSwapCharacterEncryptHello() ✔
│     ├─ testEncryptHello() ✘ Tests encrypting Hello to Hlloe ==> expected: <Hlloe> but was: <oeeo>
│     ├─ testMoveCharacterEncryptHello() ✘ Tests moveCharacter for string of length 5 ==> expected: <oelHl> but was: <lHHl>
│     ├─ testSwapSubstringsEncryptHello() ✘ Tests swapSubstrings if length is odd and not divisible by three ==> expected: <Hlloe> but was: <lHlloe>
│     └─ testMoveCharacterInvalid() ✔
└─ JUnit Vintage ✔

testMoveCharacterEncryptHello

testMoveCharacterEncryptHello has different expected (“oelHl”) and actual (“lHHl”) results.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
  JUnit Jupiter:SecretMessageTest:testMoveCharacterEncryptHello()
    MethodSource [className = 'SecretMessageTest', methodName = 'testMoveCharacterEncryptHello', methodParameterTypes = '']
    => org.opentest4j.AssertionFailedError: Tests moveCharacter for string of length 5 ==> expected: <oelHl> but was: <lHHl>
       org.junit.jupiter.api.AssertionUtils.fail(AssertionUtils.java:55)
       org.junit.jupiter.api.AssertionUtils.failNotEqual(AssertionUtils.java:62)
       org.junit.jupiter.api.AssertEquals.assertEquals(AssertEquals.java:182)
       org.junit.jupiter.api.Assertions.assertEquals(Assertions.java:1135)
       SecretMessageTest.testMoveCharacterEncryptHello(SecretMessageTest.java:32)
       java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
       java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
       java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
       java.base/java.lang.reflect.Method.invoke(Method.java:568)
       org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:686)
       [...]

Unlike the previous failing test case, the stacktrace only lists the test program and not the source code:

1
SecretMessageTest.testMoveCharacterEncryptHello(SecretMessageTest.java:32)

We examine line 32 of SecretMessageTest.java to see the failing test case:

1
2
3
4
5
6
7
8
9
    /**
     * Tests moveCharacter method - Move character at index (length - 2) to the end
     * of the string
     */
    @Test
    public void testMoveCharacterEncryptHello() {
        assertEquals("oelHl", SecretMessage.moveCharacter("oellH"),
                "Tests moveCharacter for string of length 5");
    }

The call to assertEquals tells us the following about our test case:

  • Input: “oellH”
  • Method testing: SecretMessage.moveCharacter
  • Expected output: “oelHl”
  • Actual output: SecretMessage.moveCharacter("oellH")

Next we examine moveCharacter of SecretMessage.java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
    /**
     * Returns results of *Move character* step of encrypt and decrypt algorithms.
     * Works for messages of length two or greater.
     * 
     * @param message message to encrypt or decrypt
     * @return message that results from *Move character* step of encrypt and
     *         decrypt algorithms
     * @throws IllegalArgumentException if length of 0 or 1
     */
    public static String moveCharacter(String message) {
        int length = message.length();
        if (length < 2) {
            throw new IllegalArgumentException("Invalid string");
        }
        return message.substring(length - 2) + message.substring(length - 1)
                + message.charAt(length - 2);
    }

To find the bug, we will need to trace the method with message as “oellH” and consider where it does match our given algorithm:

Move character at index (length - 2) to the end of the string.

Both calls to substring get the right portion of the message. We need to consider how we would get the left portion of the message, which will help us debug the problem.