In this section, two of the most used Java frameworks for unit testing are shown and briefly commented on. We will focus on their syntax and main features by comparing a test class written using both JUnit and TestNG. Although there are slight differences, both frameworks offer the most commonly-used functionalities, and the main difference is how tests are executed and organized.
Let's start with a question. What is a test? How can we define it?
In the programming approach, there are several types of tests depending on their scope: functional tests, acceptance tests, and unit tests. Further on, we will explore each of those types of tests in more detail.
Unit testing is about testing small pieces of code. Let's see how to test a single Java class. The class is quite simple, but enough for our interest:
public class Friendships { private final Map<String, List<String>> friendships = new HashMap<>(); public void makeFriends(String person1, String person2) { addFriend(person1, person2); addFriend(person2, person1); } public List<String> getFriendsList(String person) { if (!friendships.containsKey(person)) { return Collections.emptyList(); } return friendships.get(person); } public boolean areFriends(String person1, String person2) { return friendships.containsKey(person1) && friendships.get(person1).contains(person2); } private void addFriend(String person, String friend) { if (!friendships.containsKey(person)) { friendships.put(person, new ArrayList<String>()); } List<String> friends = friendships.get(person); if (!friends.contains(friend)) { friends.add(friend); } } }
JUnit (http://junit.org/) is a simple and easy-to-learn framework for writing and running tests. Each test is mapped as a method, and each method should represent a specific known scenario in which a part of our code will be executed. The code verification is made by comparing the expected output or behavior with the actual output.
The following is the test class written with JUnit. There are some scenarios missing, but for now we are interested in showing what tests look like. We will focus on better ways to test our code and on best practices later in this book.
Tests classes usually consist of three stages; set up, tests and tear down. Let's start with methods that set up data needed for tests. A setup can be performed on a class or method level:
Friendships friendships; @BeforeClass public static void beforeClass() { // This method will be executed once on initialization time } @Before public void before() { friendships = new Friendships(); friendships.makeFriends("Joe", "Audrey"); friendships.makeFriends("Joe", "Peter"); friendships.makeFriends("Joe", "Michael"); friendships.makeFriends("Joe", "Britney"); friendships.makeFriends("Joe", "Paul"); }
The @BeforeClass
annotation specifies a method that will be run once before any of the test methods in the class. It is a useful way to do some general setup that will be used by most (if not all) tests.
The @Before
annotation specifies a method that will be run before each test method. We can use it to set up test data without worrying that the tests that are run afterwards will change the state of that data. In the example above, we're instantiating the Friendships
class and adding five sample entries to the Frienships
list. No matter what changes will be performed by each individual test, this data will be recreated over and over until all the tests are performed.
Common examples of usage of those two annotations is setting up of database data, creation of files needed for tests, and so on. Later on, we'll see how external dependencies can and should be avoided using mocks. Nevertheless, functional or integration tests might still need those dependencies and the @Before
and @BeforeClass
annotations are a good way to set them up.
Once data is set up, we can proceed with the actual tests:
@Test public void alexDoesNotHaveFriends() { Assert.assertTrue("Alex does not have friends", friendships.getFriendsList("Alex").isEmpty()); } @Test public void joeHas5Friends() { Assert.assertEquals("Joe has 5 friends", 5, friendships.getFriendsList("Joe").size()); } @Test public void joeIsFriendWithEveryone() { List<String> friendsOfJoe = Arrays.asList("Audrey", "Peter", "Michael", "Britney", "Paul"); Assert.assertTrue(friendships.getFriendsList("Joe").containsAll(friendsOfJoe)); }
In this example, we are using a few of the many different types of asserts. We're confirming that Alex does not have any friends, while Joe is a very popular guy with five friends (Audrey, Peter, Michael, Britney, and Paul).
Finally, once the tests are finished, we might need to perform some clean up:
@AfterClass public static void afterClass() { // This method will be executed once when all test are executed } @After public void after() { // This method will be executed once after each test execution }
In our example, in the Friendships
class, we have no need to clean up anything. If there were such a need, those two annotations would provide that feature. They work in a similar fashion to the @Before
and @BeforeClass
annotations. @AfterClass
is run once all tests are finished. The @After
annotation is executed after each test. This runs each test
method as a separate class instance. As long as we are avoiding global variables and external resources such as databases and APIs, each test is isolated from the others. Whatever was done in one, does not affect the rest.
The complete source code can be found in the FriendshipsTest
class in the https://github.com/TechnologyConversations/tdd-java-ch02-example-junit.git or https://bitbucket.org/vfarcic/tdd-java-ch02-example-junit.git repositories.
In TestNG (http://testng.org/doc/index.html), tests are organized in classes, just as in the case of JUnit.
The following Gradle configuration (build.gradle
) is required in order to run TestNG tests:
dependencies { testCompile group: 'org.testng', name: 'testng', version: '6.8.21' } test.useTestNG(){ // Optionally you can filter which tests are executed using // exclude/include filters //excludeGroups 'complex' }
Unlike JUnit, TestNG requires additional Gradle configuration that tells it to use TestNG to run tests.
The following test class is written with TestNG and is a reflection of what we did earlier with JUnit. Repeated imports and other boring parts are omitted with the intention of focusing on the relevant parts:
@BeforeClass public static void beforeClass() { // This method will be executed once on initialization time } @BeforeMethod public void before() { friendships = new Friendships(); friendships.makeFriends("Joe", "Audrey"); friendships.makeFriends("Joe", "Peter"); friendships.makeFriends("Joe", "Michael"); friendships.makeFriends("Joe", "Britney"); friendships.makeFriends("Joe", "Paul"); }
You probably already noticed the similarities between JUnit and TestNG. Both are using annotations to specify what the purposes of certain methods are. Besides different names (@Beforeclass
vs @BeforeMethod
), there is no difference between the two. However, unlike Junit, TestNG reuses the same test class instance for all test methods. This means that the test methods are not isolated by default, so more care is needed in the before
and after
methods.
Asserts are very similar as well:
public void alexDoesNotHaveFriends() { Assert.assertTrue(friendships.getFriendsList("Alex").isEmpty(), "Alex does not have friends"); } public void joeHas5Friends() { Asert.assertEquals(friendships.getFriendsList("Joe").size(), 5, "Joe has 5 friends"); } public void joeIsFriendWithEveryone() { List<String> friendsOfJoe = Arrays.asList("Audrey", "Peter", "Michael", "Britney", "Paul"); Assert.assertTrue(friendships.getFriendsList("Joe").containsAll(friendsOfJoe)); }
The only notable difference when compared with Junit is the order of the assert
variables. While JUnit assert's order of arguments is optional message, expected values, and actual values, TestNG's order is actual value, expected value and optional message. Besides the difference in the order of arguments we're passing to the assert
methods, there are almost no differences between JUnit and TestNG.
You might have noticed that @Test
is missing. TestNG allows us to set it on the class level and thus convert all public methods into tests.
The @After
annotations are also very similar. The only notable difference is the TestNG @AfterMethod
annotations that acts in the same way as the JUnit @After
annotation.
As you can see, the syntax is pretty similar. Tests are organized in to classes and test verifications are made using assertions. That is not to say that there are no more important differences between those two frameworks; we'll see some of them throughout this book. I invite you to explore JUnit (http://junit.org/) and TestNG (http://testng.org/) by yourself.
The complete source code with the examples above can be found at https://bitbucket.org/vfarcic/tdd-java-ch02-example-testng.git.
The assertions we have written until now are using only the testing frameworks. However, there are some test utilities that can help us make them nicer and more readable.
3.144.37.38