How To Write JUnit Tests
In some previous posts, I’ve made some observations on unit tests, and added to the noise of how to do advanced unit testing with a faux Servlet container. Recently I’ve been asked “how do you get started writing unit tests?”
I had to give great thought to the question. A lot of us have been writing unit tests for a long time, most using JUnit or a similar suite, so to many of us it’s kind of second nature. After pondering and chatting more, here’s what I came up with.
What is Unit Testing?
Before we get into writing tests, let’s first look at why we write unit tests, and even before that, let’s look at what unit tests are.
Wikipedia tells us that Unit Testing has to do with identifying and testing the smallest bits of software. Of course, our software is made up of a bunch of little pieces, called procedures, functions, or methods as you prefer. In Java, our software is made up of a collection of classes that are filled with methods; the methods do the work. From start to end, programs are just series of methods calling methods, passing and returning data, branching, and so on. If we look at just one method at a time, we’ve got a unit that we can test.
Beyond that discussion, there’s a line drawn by many between what “unit testing” truly means. This line is the difference between “unit” testing and “integration” testing. In general, if you “leave” your method, especially if you leave the memory space of the application (for example, to write a file, make a network call using a URL, or access a database), you’ve crossed that line between unit and integration testing. For our purposes, we’re not going to heed that distinction. If you get to a bit of code that makes use of an external resource, you need to be sure that resource is available or that it can be “faked” with something such as EasyMock or HTTPUnit or DbUnit. We’ll leave that off for now, otherwise this article would become a small book.
Why Unit Test?
As for why we write tests, there are many theories and reasons. A good, common, baseline reason for writing tests is that we want to be sure that the software does what we expect it to do. That’s really all it is. A good test suite will make sure that for different inputs, the correct expected outputs or actions happen. A good test suite will also make sure that for bad inputs, correct handling occurs and the expected outputs or actions still happen.
If you’re like me, and you like writing software, writing unit tests is a good way to get more of what you like. You’ve got to write a method to do some work, and you have to write some methods to test that method (or in some disciplines, the other way around). Plenty of software to write!
If you’re one of those that doesn’t like to so much write software, unit testing is a good way to make sure that you can get the thing done and move on to the next thing. Write the method, make sure it works, move along.
There’s a strict methodology called “Test Driven Development” where the test is written first, and due to its failure, you’re “allowed” to write the software necessary to fix it. I prefer to go with either “Test During Development” where you write the tests to complement and shore-up the methods as you write them, or “Test Defended Development” where the tests are in place to be sure that the methods do what is expected and provide feedback on future changes.
There are a lot of developers who don’t want to be bothered with unit testing. The alternatives are just as tried and true, but generally rely on a person (or perhaps a script) actually using the application being developed. This has a few weaknesses such as an inability to as quickly test different cases, especially failure cases. It also has the inability to easily test each branch of all parts of the application, as the conditions required to hit each side of each if statement can be non-trivial to configure.
Unit testing allows directly calling each lowest-level method, providing the parameters necessary to hit each part of each condition. It’s far easier to ensure that all aspects of a program will react in an expected manner when each little bit is tested directly.
Of course, this doesn’t replace the higher-level testing. Additionally, it’s often prudent to test “bigger units” by calling methods that call other methods, to be sure that the programmatic
Why JUnit?
Writing tests can be done with or without a framework. I’ve already decided this will be focused on JUnit, but let’s consider testing without a framework. In this case, specifically in Java, another class might be written to use the methods of the class that will do the real work. This test class would probably have some form of main method, so it could be started. The test class then has a number of methods called (through some path) from that main method, and they, in turn, call the methods being tested. Here’s a simple example of such a class and test class pair.
class Foo { void foo(){} } class FooTest{ public static void main(String[] args) { new FooTest().testFoo(); } void testFoo(){ new Foo().foo(); } }
In a project that has a lot of classes, or with classes that have a lot of methods, that’s a lot of main classes that need to be created. It could also be the case that there’s just one test class with a main method that then calls the test methods in all of the test classes, but then you can’t run just one or a few tests.
Additionally, while this trivial example simply shows a skeleton of how to handle starting to test another class, it doesn’t begin to show how to handle test failures without ending the test run. It barely gives us an idea of how we’d have to scale to test additional methods, or different inputs, or error conditions. There’s a lot of Exception handling that would have to go on in our test class to handle when things go wrong (and we unfortunately have to expect things to occasionally go wrong).
JUnit (and other frameworks) takes care of this busy work for you. Especially if you’re comfortable with annotations, writing unit tests can be as easy as simply writing shadow classes.
class Foo { void foo(){} } @RunWith(JUnit4.class) class FooTest{ @Test void testFoo(){ new Foo().foo(); } }
These trivial examples don’t really test anything, but it shows that just eliminating the boilerplate makes it neater to read. Also, since we’re falling back on the test runner (the annotation at the top of our class gives us that hint), we know we can rely on the framework to allow each or our Test-annotated methods to fail and handle it appropriately, allowing other tests to still be run. Once one is familiar with the various JUnit annotations, the possibilities start to become rather exciting, in that geeky way.
What does JUnit bring to us to make our tests easier?
Annotations
We’ve already glossed over the RunWith and Test annotations. Here are a few of the other frequently used annotations that you may see or use in your test classes.
- After – may be applied to a method that executes after each test method is run, useful for post-method clean-up regardless of test successs
- AfterClass – may be applied to a method that executes after all of the methods in a test class have run, which can be useful for clean-up
- Before – may be applied to a method that executes before each test method is run, useful for pre-method preparation
- BeforeClass – may be applied to a method that executes before any test method in the class is run, useful for preparation
- Ignore – may be applied to a method annotated with @Test or a class annotated with @RunWith to skip, but still count, the method (or class)
- Test – may be applied to a method to let JUnit know that is a test that should be run
Even within these annotations there are additional bits that can be added to make them even more useful. For example, Test (by far our most commonly used annotation) allows providing a time-out value to stop and fail a long running test that runs too long, as well as an expected value for when a test method is expected to throw an exception, allowing a cleaner test class.
There are more annotations that one can use in their test classes, but these are the most common.
Asserts
The bulk of tests that we’re able to do with JUnit are types of assertions. Wikipedia says an assertion is “a predicate (for example a true–false statement) placed in a program to indicate that the developer thinks that the predicate is always true at that place.” JUnit seems to embrace that, as just about everything we’ll be doing to confirm our expectations is in the junit.framework.Assert class. They fall under some fairly expected names.
- assertEquals – makes sure the two things compared are “equal”
- assertFalse – makes sure the thing has a value of “false”
- assertNotNull – makes sure the thing has a value
- assertNotSame – makes sure two things are not the actual same thing
- assertNull – makes sure the thing does not have a value
- assertTrue – makes sure the thing has a value of “true”
Additionally, there are a set of failures that can be used to notify JUnit that something is not as expected, but that perhaps does not fit into a validation made by an assertion. These are very commonly used, so we can import them and use them statically, by adding imports such as import static junit.framework.Assert.assertEquals;
to our list of imports to allow us to just use the method as if it were our own.
Getting JUnit
Depending on the build environment you’re going to use, it may be the case that you need to get JUnit and add it to your project. If you’re using a framework like Maven to control your dependencies, or Spring for your project, it may be the case that JUnit has tagged along. Using rich IDEs like Eclipse, Idea, and NetBeans already have JUnit available to add to your project. If you need to, trek over to junit.org and grab the latest JAR file.
There are some differences in the versions of JUnit, as one might expect, so we’re going to carry on and expect JUnit 4 is being used. Simply add the JUnit JAR to your class path (skipping any discussion of separation of test and releasable source for now) by whatever means is appropriate.
When using a robust IDE, like NetBeans or Idea, or an external build environment like Ant or Maven, that allow separate classpaths in the same project, make sure that JUnit is only in the test classpath to help prevent accidentally placing or using test classes in real code. Eclipse, unfortunately, at the time of this writing, doesn’t support separate classpaths for folders in the same project, so the way to do this in Eclipse is to have a project for the “real” source and another for the “test” source, or very carefully segregate your source into separate folders.
That’s pretty much the bulk of getting prepared with JUnit.
Writing Unit Tests
Now that we’re set-up, let’s start writing some code.
Creating Classes
We’re going to make two classes. For the purposes of this discussion they can be in the same folder. For a real, or more robust environment we’d have separate test and base source folders, but still probably make the classes in the same package. Even the package placement isn’t a requirement, but it does make it possible to test protected methods, which makes protected a common accessor (instead of private). Security, thanks to reflection, is really more of a compile-time suggestion than a run-time restriction, after all, but I think I’m getting ahead of myself.
For simplicity and brevity, I’m going to move forward assuming Eclipse is being used, leaving it to the reader to make the appropriate changes for your environment. Create a new Java project. Give it a name, pick the JDK/JRE to use, and after bonking the first “next” button, while still in the new project dialog, add the Library for JUnit by picking the Library tab, clicking the “add library” button, and choosing JUnit from the list, finally choosing JUnit4 when offered. By default, Eclipse will want to open the Java perspective (if not already the one in use), and show your project in the Project Explorer view. If your defaults are like mine, in your new project you’ll see a “src” folder, the JRE System Library, and the JUnit4 library in the view. If you’re missing the JUnit, go to the project properties, find the build path, find the libraries tab, and add the JUnit library.
Base Class
Let’s start by making a base class. This will represent the “real” class in our project; one that would do some real work. (Many ways to do this, but this one is most certain across platforms…) Select the src folder, and then in the File menu pick New, and then Class. Give the class a name and optionally a package. I’m going with Foo in the default package; of course in a real project you’d be dividing your classes up in a way that makes sense. Eclipse should give you a tiny class file that looks thus:
public class Foo { }
Of course, with your name and optionally a “package” statement.
Test Class
Let’s compliment this with our corresponding test class. This will be the class in which we will place the methods to test our base class. In a real project, we’d separate this in either a separate source folder or (since Eclipse can’t as easily separate sources in a project) a separate test project. The convention is to append the name of the class that we’ll be testing with “Test”; in my case, then, it becomes “FooTest.” Typically, these classes are placed in separate folders, as mentioned, but in the same package. In the case where more than one test class is needed, either for controlling the size of the class or to separate test functionality, additional classes would be made by appending to that class name, such as “FooTestMore” or “FooTestDatabase.”
Again, letting Eclipse do the building for us, we’ll append the name of “Test” to our class name, put in the same package, and end up with something like this.
public class FooTest { }
Let’s quickly make our test class into, well, a test class. We’ll jump in with annotations to keep our code tidy. Before the class, add the annotation @RunWith(), and give it the “parameter” of JUnit4.class, kind of thus:
import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @RunWith(JUnit4.class) public class FooTest { }
We could have put the fully-qualified name in instead of using the imports, but we’ll benefit from using the imports for the other test parts that we’ll get into in a moment. What the RunWith annotation gives us is a runtime JUnit helper that will tell JUnit that this class contains tests to run.
We can run the tests either directly on our class, the package, the source folder, or even the project and JUnit will figure out which classes contain tests, and which methods in those classes are to be run with our tests. We can test this in Eclipse by ensuring our test class is highlighted in the Package Explorer, and hit the Run menu, Run As item, and finally the JUnit Test item. If we do this now, we’ll get a failure, however, because we haven’t got any test methods.
Creating Test Methods
Now that we’ve got our classes configured, let’s get to making some tests. In a typical project, we’d have some tasks that we knew the program would need to be done. Since we’re not working toward any particular goal, let’s instead look at the kinds of things we can test, and make up some methods that allow us to make use of these.
A good way to write tests is to “manage expectations” and “validate results.” What this means is that we may want to do a little prep before calling our methods, and then check the results after our call, to be sure expected things have happened. Let’s make some simple examples of prep and validate testing.
Let’s suppose we have a need to have a method to verify that a string meant for a password meets a few criteria: it must be longer than six characters (to be hard to crack), shorter than 20 characters (since that’s all the room we have to store them in our database), and must not contain spaces (just to have something negative to check for), must contain at least each of a one capital letter, lower-case letter, number, and “special” character (such as periods, exclamation, question, octothorpe…most of the other things we can easily type). Let’s use a test-driven methodology, as it lets us focus on writing the tests first.
So that we don’t have a compile failure, let’s add a stub for our method to our main class. We’re validating a password, so it’ll just take a String and return a boolean indicating its adherence to our rules. In our base class, let’s create the method validatePassword() as something like this:
public class Foo { public static boolean validatePassword(final String password){ return false; } }
Note that it does return false, meaning everything we give it will be deemed unacceptable. After we write some tests, we’ll fill in our method to be sure they all pass. The first round, we’ll do as we write the test, but then we’ll fly through some tests and satisfy them all at once.
Also, I’ve cheated and made this method static, just to make it easier to test. Of course, in a more fully-functional class, there’d probably be instance variables and private or protected worker methods.
For a first test, let’s verify that the password must have a value. We can test this by passing it a null parameter and an empty string. In our test class, let’s create a method to test null passwords. The convention for test methods is to start the name with the method being tested, and then extend it with a description of the test. Since we’re validating the validatePassword() method with null passwords, we’ll create a method validatePasswordNull(). To tell JUnit that this is a test method, we’ll annotate the method with @Test (adding the necessary import as well). Since we expect null passwords to fail, we’ll simply test our assumption using the assertFalse() test. This gives us the following test class:
import static junit.framework.Assert.assertFalse; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @RunWith(JUnit4.class) public class FooTest { @Test public void validatePasswordNull() { assertFalse(Foo.validatePassword(null)); } }
This we can run using the trick mentioned above (Run menu, Run As, JUnit Test). Instead of a failure, we’ll see that we pass our test! This is a little inconclusive, however, as the validatePasword() method will always return false, but it’s a good start and gives us a little code to defend. In a moment, we’ll see that we’ll need to add a check for null to our method, as this test will cause a failure if we try to act on our null password! But let’s not give too much away.
Let’s add the second test we’ve already mentioned, for an empty string.
import static junit.framework.Assert.assertFalse; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @RunWith(JUnit4.class) public class FooTest { @Test public void validatePasswordEmptyString() { assertFalse(Foo.validatePassword("")); } @Test public void validatePasswordNull() { assertFalse(Foo.validatePassword(null)); } }
Again, running the test will show that the tests both pass, as expected. The astute reader will note that these two tests were not strictly in our requirements. One can argue that a null or empty string fall outside of our length parameters, but I really wanted both a quick start and a couple of tests that will cause trouble, as I purposefully expose some potential pitfalls as we progress. With that, let’s move onto our requirements, and with this, we’ll start adding to our base class to make them work.
Let’s make a method that makes sure our password is longer than 6 characters. We’ll simply create a method that checks strings of shorter lengths ensure they’re not valid. Here’s where programmer’s preferences start to kick in. We can make a number of separate test classes, each passing in strings of different lengths. We can make one method making several validatePassword() calls. We can put this in a loop. Some may see the importance of the value of the string and want to mix that up, too. Since the rule is that a password must be longer than 6 characters, the content doesn’t matter, and the method of calling doesn’t matter, so I’m going to be concise and make one method that simply sends a string too short to pass by passing 5 characters. Since this is similar to the rule for too long, I’m going to make a simple method that tests for a string too long by passing 21 characters.
@Test public void validatePasswordTooLong() { assertFalse(Foo.validatePassword("123456789012345678901")); } @Test public void validatePasswordTooShort() { assertFalse(Foo.validatePassword("12345")); }
To keep the post from getting way too long, I’ve just included the test methods here; inject them in the test class. Running the test still shows the validatePassword() method works as expected. So far, so good. Of course, we haven’t yet had to alter our base class beyond adding the stub. What we’ve asserted is only that our set of bad passwords will not be validated. Let’s quickly throw the rest of the expected failures in otherwise correct-length passwords:
@Test public void validatePasswordNoCaps() { assertFalse(Foo.validatePassword("!10therwisevalid")); } @Test public void validatePasswordNoLowers() { assertFalse(Foo.validatePassword("!10THERWISEVALID")); } @Test public void validatePasswordNoSpaces() { assertFalse(Foo.validatePassword("!1Otherwise Valid")); } @Test public void validatePasswordNoSpecial() { assertFalse(Foo.validatePassword("M1ss1ngSp3c14l")); }
So now we’ve got a test class with eight methods that validate all of the cases we would expect to fail. We’ve got no string and empty string. We’ve got too-short and too-long strings. We’ve got a set of strings that are the right lengths, but are each missing critical parts. We’ve got a string with the right length, and right parts, but with a space in it, which is not allowed. Running the test will pass, and we can see that all of the invalid passwords are correctly failing the validation. Of course, everything fails as our method only returns false.
Let’s then add one final test method that should pass, which we know will fail, which will then allow us to complete our base class.
@Test public void validatePassword() { assertTrue(Foo.validatePassword("Good1!")); assertTrue(Foo.validatePassword("Expect4Success!")); assertTrue(Foo.validatePassword("1LongEnough?YesItIs!")); }
This method has everything we expect. The first string is six characters, which is our minimum. The middle string is 15 characters, which is more than 6 and fewer than 20. The last string is 20 characters, which is our maximum. Each contains no spaces. Each has at least one each of lower-case and upper-case letters, numbers, and special characters. Note the assertion has been changed to True (and we’ll have to include the correct import). Below is the set of full test cases that we have to ensure our validatePassword() method works.
import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @RunWith(JUnit4.class) public class FooTest { @Test public void validatePassword() { assertTrue(Foo.validatePassword("Good1!")); assertTrue(Foo.validatePassword("Expect4Success!")); assertTrue(Foo.validatePassword("1LongEnough?YesItIs!")); } @Test public void validatePasswordEmptyString() { assertFalse(Foo.validatePassword("")); } @Test public void validatePasswordNoCaps() { assertFalse(Foo.validatePassword("!10therwisevalid")); } @Test public void validatePasswordNoLowers() { assertFalse(Foo.validatePassword("!10THERWISEVALID")); } @Test public void validatePasswordNoSpaces() { assertFalse(Foo.validatePassword("!1Otherwise Valid")); } @Test public void validatePasswordNoSpecial() { assertFalse(Foo.validatePassword("M1ss1ngSp3c14l")); } @Test public void validatePasswordNull() { assertFalse(Foo.validatePassword(null)); } @Test public void validatePasswordTooLong() { assertFalse(Foo.validatePassword("123456789012345678901")); } @Test public void validatePasswordTooShort() { assertFalse(Foo.validatePassword("12345")); } }
If we run this test, we’ll get eight passes and one failure. The failure is currently for our expected successful call with the valid password. This has given us a set of tests we can use to verify that we won’t make software that breaks our expected failures either.
So let’s start addressing our base class, and try to make a method that meets all of our test requirements. As a reminder, the requirements are that it must be longer than six and shorter than 20 characters, and must not contain spaces, must contain at least each of a one capital letter, lower-case letter, number, and special character.
public class Foo { public static boolean validatePassword(final String password) { boolean special = false, digit = false, upperCase = false, lowerCase = false; if (password != null && password.length() > 5 && password.length() < 21) { for (int index = 0; index < password.length(); index++) { final Character character = password.charAt(index); if (Character.isWhitespace(character)) return false; else if (Character.isLowerCase(character)) lowerCase = true; else if (Character.isUpperCase(character)) upperCase = true; else if (Character.isDigit(character)) digit = true; else if (33 <= character.charValue() && 127 >= character.charValue()) special = true; } } return special && digit && upperCase && lowerCase; } }
Here, again, a lot of programmer preference may be used to determine a best way to do it. Probably the shortest and easiest would have been to use a Pattern or regular expression, but this allows us to see each bit, and compare it to our tests.
Looking at our method, we can see that we make a placeholder for each type of desired character in our password; each is set to false as we’re starting with the assumption that they don’t exist and we’ll be working to prove otherwise. We start by checking that the password is the right length. Note also that there’s a quick null-check there, too; this wasn’t in the requirements, ‘though we did write a test for it, but it is essential for program safety as the password.length() would throw a NullPointerException if we didn’t check and a null was passed. Then we iterate over each character in the string, checking to see if it meets our requirements. Upon finding a space (or other whitespace character such as tab or newline) we stop and return false. There is a little cheat in the “detection” of special characters as we assume any ASCII printable character that isn’t a number or letter (found in the earlier branches of our conditions) is acceptable.
If we run the test methods now, we’ll see that all nine of them pass. All three of our valid passwords are accepted, and none of the other eight expected failures are.
This is a trivial example of using the asserts, showing only true and false matches, but we can see that the test methods are pretty brief, easy to read, and don’t require a lot of effort in preparation or evaluation.
That’s a pretty bad example of tests. For example, you should be using data driven testing for your password tests. In TestNG, it would look like:
@DataProvider
public Object[][] goodPasswords() {
return new Object[][] {
new Object[] { “password1” },
new Object[] { “password2”},
…
}
}
@Test(dataProvider = “goodPasswords”)
public void good(String password) {
// assert password
}
Much less verbose and easier to maintain.
JUnit 4 has a similar, although more contrived, mechanism.
I have also implemented a JUnit plugin to provide better parameterized tests to JUnit (similar to TestNG). As the internal structure of TestNG is a bit different from JUnit, it’s not completely equal but IMHO it provides the most important features for parameterized testing.
See https://github.com/TNG/junit-dataprovider
Feedback welcome 🙂
While I appreciate your feedback, and accept that other frameworks have other valid and potentially smaller prep and test methods, I have to point out a few simple things:
First, this wasn’t an assertion that JUnit is the be-all or end-all of test suites. It’s just easy to use and is fairly ubiquitous. The intent was to give some insight and validation to writing unit tests, using the chosen framework that prompted the discussion.
Second, your example seems to try to offer a way to test every potential value, while mine hit only the boundary cases and a few other examples. I would contend that your example doesn’t really add any value; listing a large number of valid passwords, or even a lot of invalid ones, isn’t any “more” of a test. With this, data-driven testing for this example is almost excessive; although certainly it would have value in other situations.
Third, your example not only fails the validation (as password1 is not valid given the example rules), but also does not actually test anything in your “good” method, which is odd since the invocation and test is nearly as long as your comment, and also does not test all of the expected cases.
Finally, I suspect that if you wrote a comprehensive test, even as trivial as I did, that you would find that your less verbose and contrived framework would not in fact be any less effort than the nine methods (or eleven invocations) that my test class contained. Again, there are certainly cases where this would not be the case, such as automated integration tests, Visitor patterns, and the like.