Skip to content

HW4: Krypton

Krypton Logo

Learning Objectives

This homework assignment is designed to help you understand unit testing and debugging. This includes an understanding of black-box/closed-box testing, white-box/open-box testing (and the JUnit framework), test coverage (and the EclEmma/JaCoCo framework), the debugging process, and instrumentation techniques.

Motivation

Thus far, your testing has been both haphazard (i.e., you have not thought carefully about how to test your code to ensure that it is correct) and ad hoc (i.e., you have not used tools that make it easier to test your code). In addition, you have probably relied to heavily on the submission system to test your code (because we have not penalized you for "excess" submissions). Going forward, you will have to do a much better job of testing your code, and this assignment is designed to help you do so.

To help motivate some of the aspects of this assignment, it is useful to briefly return to the previous assignment, test it using JUnit, consider some issues related to coverage, and think about the implications for the current assignment.

To that end, download the following file (that contains JUnit tests for the previous assignment):

BattlemintonWhiteBoxTests.java

add the file to the directory/folder hw3, and answer the following questions. (Note: You do not have to submit the answers to these questions.)

Questions About The Importance of Test Coverage

  1. RunBattlemintonWhiteBoxTests (i.e. click on the ▷ icon to the left of the class declaration for BattlemintonWhiteBoxTests). Does your code fail any of the tests? In other words, are any tests highlighted? (It should pass all of the tests if your solution is correct or you are using the solution that was provided to you.)

  2. Check how these tests covered BattlemintonEvent.java. There are two ways to do this. One way is to open BattlemintonEvent.java and select "Watch" at the very bottom of the IDE. The other way is to type Ctrl+P (or Cmd+P) the word "task" (no quotes), a space and select "Create coverage report", type the word "hw3" (no quotes), press Enter, type Ctrl+P (or Cmd+P), the word "task" (no quotes), a space, and select "Show coverage report". (Note: The second approach takes a few seconds more but provides more detailed information.) Do the tests completely cover the statements and branches in BattlemintonEvent.java?

  3. Comment-out the last statement in the testBattle() method (i.e., the call to increaseWestScore() in the BattlemintonWhiteBoxTests class), run BattlemintonWhiteBoxTests, and use the coverage tool. What is now true of the branch coverage of the points() method? Why?

  4. Why is it important to have 100% statement coverage and 100% branch coverage?

Before you proceed, undo the change you made to the BattlemintonWhiteBoxTests class.

Questions About The Need For More Than Just Coverage

  1. Temporarily, change the increaseWestScore() method in the Battle class so that it increases the scoreWest attribute by 50, and run BattlemintonWhiteBoxTests using the coverage tool. Despite the fact that the Battle class now contains a defect, does it pass all of the tests? Is the Battle class completely covered?
  2. Does the BattlemintonWhiteBoxTests class test the increaseWestScore() method or does it just execute it? Does the coverage tool indicate that statements/branches have been tested, or just that they've been executed?
  3. Add the statement assertEquals("Deadpool 10, Wolverine 0", battle.toString(), "increaseWestScore()"); to the end of the testBattleMethod() and rerun the tests. Does the code now pass all of the tests?
  4. Correct the defect in the Battle class and rerun the tests. Does the code now pass all of the tests?
  5. In the BattlemintonEvent enum, temporarily change the speed of the PROPELLER to 900 rather than 90, and launch BattlemintonWhiteBoxTests using the coverage tool. Is the BattlemintonEvent enum completely covered? Despite the fact that the BattlemintonEvent enum now contains a defect, does it pass all of the tests? Why?
  6. Is it enough to just cover code when writing unit tests?
  7. What are the implications of your answer to the previous questions? In other words, what must you be sure to do when you are required to write and submit unit tests for future assignments?

Before you proceed, undo the change you made to the BattlemintonEvent class.

Questions About Unit Testing

  1. Which testing process was easier to use, the one that you used for the previous assignment or the one you just used? (Focus just on the process and ignore whether you had to write the tests or not.) Why?
  2. If you become a professional programmer, there will not be a submission system for you to rely on. Instead, what must you do to ensure that your code is correct? With that in mind, what must you now start doing this semester?

Overview

The (fictitious) company SuperCypher has written a Krypton class (from the Greek word κρυπτός which means "the hidden one") that can be used to encrypt and decrypt messages using a secret key phrase. They have also written a suite of white box tests that covers all statements and branches in this class. They have come to you to do additional testing of the Krypton class and to debug it if necessary. (Hint: It will be necessary to debug the Krypton class. In other words, it does contain errors.)

Definitions

Black-Box/Closed-Box Tests
Tests that is based on specifications, rather than the actual implementation of the code.
White Box/Open-Box Testing
Tests that are based on the actual implementation of the code.
Heuristic
An acceptable approximation.
Rule of Thumb
A guideline that is informed by experience.
Random Tests
Tests with inputs that were chosen at random by the tester. As with any test, it includes the expected output.

Existing Classes

SuperCypher has provided you with an implementation of the Krypton class and a white box test suite (written in Java using the JUnit framework) that covers all statements and branches in the Krypton class.

Krypton.java

WhiteBoxTests.java

The specifications for the Krypton class are summarized in the following UML class diagram:

Class Diagram

(As in the past, the package information has not been included in this UML class diagram.)

A Krypton object uses a key phrase (containing all of the characters in the alphabet) to encrypt and decrypt messages. The remaining specifications are included in the comments for the class. The comments accurately describe what the code must do, not what it actually does (since the code may contain errors).

Your Tasks

You must test and debug the Krypton class that you have been given. The end result must be a correct version of the Krypton class with every change you make documented in the comments. Specifically, you must:

  1. Create a public class named HeuristicTests.java (in the hw4 package) that contains JUnit tests based on "rules of thumb" about the inputs for all of the methods in the Krypton class.
  2. Create a public class named RandomTests.java (in the hw4 package) that contains JUnit tests based on random inputs for all of the methods in the Krypton class.

Note

The RandomTests class should not use the Random class in Java. You should construct inputs randomly by hand (along with the expected outputs) before you create the JUnit code. Nothing random should happen at run-time.

Note

Your test classes must be declared to be public. Do not omit the visibility/accessibility modifier.

Both classes must eventually have at least five methods. These methods must be preceded by the JUnit @Test annotation and must have names that begin with constructIncidenceArrayTest, checkForMissingCharactersTest, indexOfOccurrenceTest, decryptTest, and encryptTest. (They can have suffixes like decryptTest1(), decryptTest2(), etc. if you would like to have more than five in total). You should add these methods one at a time, running all of the tests each time you do so.

When the actual output of one of your tests does not equal the expected output you should understand the symptom and the trigger condition (i.e., stabilize the fault). Then, localize the fault, correct the fault (documenting the correction in the source code), and verify the correction.

It is strongly recommended that you test and debug one method at a time. One possible order is:

  1. Test the checkForMissingCharacters() method. (It is static and independent of any other methods.)
  2. Test the constructIncidenceArray() method. (It is static and independent of any other methods.)
  3. Test the indexOfOccurrence() method. (It is static and independent of any other methods.)
  4. Test the constructor. Make sure you have test cases for when it should and ahouldn't throw an exception. (It must be correct before you can test encrypt() and decrypt().)
  5. Test the encrypt() method.
  6. Test the decrypt() method.

Some Likely Rules of Thumb

There are a variety of rules of thumb that people use when testing code.

The constructIncidenceArray() and checkForMissingCharacters() Methods

These methods are passed a String that is supposed to contain some or all of the characters in ALPHABET. Some obvious parameters to pass include:

  • A String that is missing one letter
  • A String that is identical to ALPHABET
  • A String that is identical to ALPHABET with the characters in reverse
  • A String that is missing the first letter (i.e., the ' ' character)
  • A String that is missing the last letter in lower-case (i.e., the 'z' character)
  • A String that is missing the last letter in upper case (i.e., the 'Z' character)
  • A String that contains no letters

You should be able to think of other rules-of-thumb that might be likely to uncover defects in a method that is passed a String.

The indexOfOccurrence() Method

This method returns the index of the \(n^{\text{th}}\) occurrence of a char in a String. So, some obvious parameters to pass include:

  • Positive values for n
  • An n of 0
  • Negative values for n
  • Characters that don't appear in the String
  • Characters that appear once in the String
  • Characters that appear multiple times in the String

The Explicit Value Constructor

The explicit value constructor is passed a String and uses the checkForMissingCharacters() and constructIncidenceArray() methods. So, the rules of thumb should be obvious.

The encrypt() Method

The encrypt() method is passed a String that contains some or all of the characters in ALPHABET. Some obvious parameters to pass include:

  • A String that contains all of the letters in ALPHABET
  • A String that is identical to ALPHABET
  • A String that is identical to ALPHABET with the characters in reverse
  • An upper-case String
  • A lower-case String
  • A mixed-case String
  • A short String
  • A long String that contains some letters more than once
  • A String that contains a char that isn't in ALPHABET
  • A String that contains multiple chars that aren't in ALPHABET

You should be able to think of other rules-of-thumb that might be likely to uncover defects in a method that is passed a String.

The decrypt() Method

The decrypt() method is passed an int[] that contains the indexes of some or all of the characters in ALPHABET. Some obvious parameters to pass include:

  • An array with 1 element
  • A long array
  • An array in which all of the elements are the same
  • A array sorted in ascending order
  • A array sorted in descending order
  • An array containing negative values
  • An array containing zeroes

You should be able to think of other rules-of-thumb that might be likely to uncover defects in a method that is passed an int[].

Submission

You must submit (using Gradescope) a .zip file (named hw4.zip) that contains the directory/folder hw4 at the top level. It must include:

  1. Your corrected implementation of the Krypton class (with appropriate comments).
  2. Your RandomTests.java and HeuristicTests.java.

Because you should not be using Gradescope to verify your code (i.e., because you must be able to test and debug code without the help of a submission system), you will not be given hints about failed tests.

Note that your test classes do not need to comply with the style guide.

Grading

Your code will first be graded by Gradescope and then by the Professor. The grade you receive from Gradescope is the maximum grade that you can receive on the assignment

Gradescope Grading

Your code must compile (in Gradescope, this will be indicated in the section on "Does your code compile?") and all class names and method signatures must comply with the specifications (in Gradescope, this will be indicated in the section on "Do your class names, method signatures, etc. comply with the specifications?") for you to receive any points on this assignment. Gradescope will then grade your submission as follows:

Criterion Points Details
Conformance to the Style Guide (Style) 0 points (Success Required)
Passing Your Tests (SelfTests) 40 points (All or Nothing; Success Required)
Correctness (Official Tests) 60 points (Partial Credit Possible)

As mentioned above, you will not be given any hints if your code does not pass the official tests. Your job on this assignment is to test (and debug) the code you were given, not to use Gradescope to test it.

Also, since you should now be able to use the development tools on your local machine, you will not be given any hints about style defects.

Manual Grading

After the due date, the Professor may manually review your code. At this time, points may be deducted if you submit too few tests.

Relevant Programming Patterns

An understanding of the following programming patterns will help you complete this assignment:

Questions to Think About

You don't have to submit your answers to these questions, but you should try to answer them because they will help you determine whether or not you understand some of the important concepts covered in this assignment.

  1. The white-box tests completely covered the code. How could it still contain defects?
  2. Why are both heuristic tests (based on rules of thumb) and random tests necessary (in addition to white-box tests)?
  3. The encrypt() and decrypt() methods can be tested in tandem by using the output of the first as the input to the second. Why is it important to test them independently?
  4. As you know, applications written in Java must have a main class that contains a method with the signature public static void main(String[]) that is called the entry point of the application. When you write JUnit tests, you do not need to create such a class. What must the JUnit framework be providing behind the scenes?
  5. How does your answer to the previous question make it a little more convenient to use JUnit than the Test class you have used on previous assignments?
  6. Why can the checkForMissingCharacters(), constructIncidenceArray(), and indexOfOccurrence() methods be static?
  7. Similarly, why can't the decrypt() and encypt() methods be static?