Unit testing frameworks

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?

Note

A test is a repeatable process or method that verifies the correct behavior of a tested target in a determined situation with a determined input expecting a predefined output or interactions.

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

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.

TestNG

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.37.38