In-Class Lab: Using JUnit to Catch Mutants
Objectives:
- Practice creating good JUnit test cases
- Practice using JUnit framework within Eclipse
Background
We generally assess the functionality of the code we write by running it and writing tests. But how do we assess the quality of the tests? Does test suite quality and coverage matter? Yes! In some cases, knowing that code is bug-free is literally a matter of life and death.
One way that test suites are evaluated is called Mutation Testing. The idea is to have a piece of code that you believe works. Then you introduce "mutant" varieties of the code by changing one small thing - like removing a line of code, changing an operator, or changing a literal. After creating mutant code, you run the test suite on the mutants. A mutant is "killed" if a test case fails--therefore flagging the bug. The total number of mutants you successfully kill gives a measure of how good the test suite coverage is--the higher the number, the better.
For this lab, I will provide you with a method specification, one correct implementation, and 15 mutant implementations. Your job is to write a test suite that does NOT flag the correct implementation but does successfully reveal each mutant.
This is not the way you will typically write JUnit test cases -- it's just meant to help you practice and understand if you're writing good JUnit test cases.
Set Up
Clone the repository. In Eclipse, you'll do this in the Git repository view.
Now, we have to do a little set up. (If you choose to use a different IDE than Eclipse, you'll have to figure out how to do the set up in your IDE.)
- In the Git repository view, expand your repository, which is named something like catch-the-mutants-username.
- Right-click on
Working Tree
and selectImport Project
. Follow through that dialog and create the project - If there is a
J
on the project--meaning that it's a Java project, fantastic. If not- Right-click the project and select Properties
- Select Project Facets
- If necessary, click "Convert to faceted form"
- Select "Java" facet
- Click OK
- Now, we need to set up the classpath. Review: what
is the classpath?
- Right-click the project, select
Build Path -> Configure Build Path
- In the Libraries tab, click on Classpath, and then click on "Add Library...". Select "JUnit", click Next, and then choose JUnit 5 from the dropdown, and click "Finish".
- In the Source tab, if it's not already there, click "Add
Folder" and add the
src
folder. This tells Eclipse to tell Java that that's where the source code is - In the Libraries tab, check if
mutants.jar
is in the Classpath. If not, click on Classpath, and then click on "Add Jars...". Navigate and select your project -> lib -> mutants.jar and click OK.
- Right-click the project, select
Hopefully, there are no longer any red x's on your project/on the Java code files.
Understanding the Provided Code
Three important files:
revealer.MutantRevealer.java
:- This is the test file that you will complete.
mutants.Mutant.java
:- This is the interface that all mutant code implements. It provides the code’s problem description (also found below).
testthetests.RevealingMutantsEvaluator.java
:- This is the file that you should RUN. You will run it as a Java Application, NOT as a JUnit file.
Mutant Goals
All Mutants implement this method:
/**
* Returns the third shortest String in the array
*
* If there are fewer than 3 words in the array or if the array is null, the method
* should throw an IllegalArgumentException.
* If there is a tie for the third shortest, any of the tied strings is valid.
* If there is a tie for shortest or second shortest, the duplicates do not
* affect the calculation of the third shortest.
* If there is no third shortest word, then the method returns null.
* The original array should not be altered.
*
* Examples:
* thirdShortest(["a", "ab", "abc"]) returns "abc"
* thirdShortest(["a", "b", "bc", "ab", "bye", "and"]) returns "bye" or "and" because
* “a” and “b” are the shortest, “ab” and “bc” are the second shortest, and “bye” and “and” are the third shortest.
* thirdShortest(["a"]) should throw an IllegalArgumentException
*
* @param words an array of Strings
* @return a String giving the third shortest String from the array if it exists; otherwise, returns null
* @throws IllegalArgumentException if array is null or if there are fewer than 3 words in the array
*/
public String thirdShortest(String[] words);
Your Mission
Write your tests for the thirdShortest
method in the
MutantRevealer.java
file. Don’t delete the code given to
you in the method marked @BeforeAll
. That method lets my
code load a new Mutant
to test. Also, don’t rename or
move the class.
Then you will see how good your test code is by
running RevealingMutantsEvaluator
. Let me reiterate: DO
NOT run your MutantRevealer class using the Run As JUnit option. This
would run the file on one mutant only. You need to run the
RevealingMutantsEvaluator
file as a Java Application. This code in
turn will run JUnit on your code in a loop for all the mutants.
When your test suite accurately kills all mutants except Wolverine, you will see the victory message: “Good testing! YOU CAUGHT ALL THE BAD MUTANTS!”
How Does RevealingMutantsEvaluator work?
- First, MutantRevealer will be run on
Wolverine
, who is a good mutant. This mutant should pass all the tests, as it is a correct implementation of the problem. - Then the MutantRevealer will be run on 15 mutants, which have all incorrectly implemented the method. If your test accurately shows that the mutant program is incorrect (by at least one of the tests failing), then you have “killed” or "revealed" that mutant.
What kind of asserts can I write?
The Assertions class has all the different assert methods you can call.
Post-Mortem
- What are the benefits of unit testing/using JUnit? Consider if you were developing/maintaining the method: How would your testing/development process change?
- Why did the output come out in strange orders sometimes?
- Is it okay that some mutants passed some of the test cases?
- Recall the characteristics of good unit tests. How did you achieve them in your testing?