Test-Driven Development & Mock Objects

Back to schedule

Overview

One of the key principles of engineering secure software is having a testing mindset throughout development. Trust your own code only after testing it. This can be a real challenge in software development, as programming gives us so much freedom that it's easy to over-complicate our designs.

One of the best ways to maintain a tester's mindset as a developer is a practice called test-driven development (TDD). The idea behind TDD is to take unit testing to the next level: that everything must be unit tested, to the point where the functionality originates from getting the test to pass. At each step, we mentally and programmatically define our expectations of the system prior to writing our code. The TDD methodology looks like this:

  1. Write a unit test (expect a compile error!)
  2. Write the stubs for our new functionality (expect a red bar!)
  3. Write the simplest functionality to make the unit test pass (expect a green bar!)
  4. Refactor (expect a green bar!)
  5. Redesign (expect a green bar!)
  6. Go back to step 1.

Additionally, this activity covers an advanced topic in unit testing: mock objects. These are "fake" Java objects that we use to isolate a single class to be tested. We'll use some of the magical manipulations of the EasyMock library to create robust mocks.

Finally, the example we will be going through today is Human Resources, a simple skeleton of a program designed around Model-View-Controller. The View and the Model have not been implemented, and we we don't need to implement them today.

Activity

Download the project, and import into Eclipse. Go to File > Import > Existing Projects into Workspace . Choose Select archive . The dialog should look like this:

Take a look at the given classes. Note that we have one model, UserDAO (DAO==Database Access Object), and it's completely unimplemented. But our controller, ManageUsers , depends upon calls to UserDAO

Also, take a look at the given example test GetUserTest . You might recognize it from lecture. Review this briefly and make sure you understand it.

We'll need to re-run our unit tests a lot in this exercise, so let's set up a run configuration. Right-click on the project, and go to Run As > JUnit test . You should get a green bar in JUnit

We'll need to tweak our Eclipse settings so we can re-launch our unit tests quickly. Go to Window > Preferences > Run > Launching . On that pane, make sure that the Always launch the previously launched application option is checked. Close the preferences window.

Now hit Ctrl + F11 . This re-runs our unit tests. Green bar!

Let's start our TDD with a bug fix. The ManageUsers controller should be allowing numbers in the userID. So, Step 1 of TDD says that we start with a unit test. Go to GetUsersTest , and make a copy of the test lookupBobby . Change the name of the new test to lookupBobbyTables123 .

Now, change the unit test so that we're asking the controller to look up BobbyTables123 . This requires a change in two places: first, in the call to ManageUsers - the actual input to our test. But, we also need to change our expectations set on our mock object. Before, we told our mock UserDAO that it was going to get a lookup for a "BobbyTables", but now we expect successful database call of "BobbyTables123". Thus, our unit test should now look like this:

Run your unit tests ( Ctrl + F11 ). Red bar!

This red bar is exactly what we expected, because we haven't actually fixed the code yet. Go fix the code in the validate private method of ManageUsers . The regex should look like [a-zA-Z0-9]*

Re-run. Green bar!

Let's go for some new functionality entirely. We will build a method in ManageUsers that will allow us make a given user an admin. In particular, we expect the controller to do three key actions:

Back to Step 1 of TDD. Create a new unit test by right-clicking on the testing folder package and going to New > Class . Name the class MakeAdminTest , hit Finish.

Copy in our basic unit test setup for this activity. Hit Ctrl + Shift + O to "Organize Imports". Everything should compile.

public class MakeAdminTest {

	// Setup mocks
	private final IMocksControl ctrl = EasyMock.createControl();
	private final UserDAO userDAO = ctrl.createMock(UserDAO.class);
	private final User mockUser = ctrl.createMock(User.class);

	@Before
	public void init() {
		ctrl.reset();
	}

	@Test
	public void makeAdmin() throws Exception {
		// Establish expectations
		// replace this with expectations
		ctrl.replay();

		// Run & Verify
		// replace this with running the test
		ctrl.verify();
	}
}
	

Here's a breakdown of everything you see here:

We still need establish how we expect our mocks to be called. Based on the previous description, we have three expectations: authenticate the giver, lookup the user, and make the change. Here are the first two:

expect(userDAO.canAuthenticate("adminman", "abc123")).andReturn(true).once();
expect(userDAO.find("BobbyTables")).andReturn(mockUser).once();

To make this compile, you'll need this line at the top of your test class:

import static org.easymock.EasyMock.*;

Our expectations are this: an authentication call will be made for administrator "adminman" with password "abc123". When that call is made, the method returns a boolean true . This method can only be executed once on the mock object, otherwise the test fails. Note: expect is another method call from EasyMock that sets up this clever little chain.

For the second expectation, BobbyTables is looked up in the database. When that call is made, we return another mock object, mockUser . Allow that once. (Note: by making a User mock, we are establishing the implicit expectation that none of User's calls are to be made, since we didn't establish any.)

Now fill in the third expectation for the test: making the call to UserDAO that BobbyTables was made an admin.

Finally, we need to put in our call to ManageUsers . Add this line to the Run and Verify section.

new ManageUsers(userDAO).makeAdmin("adminman", "abc123", "BobbyTables");

After hitting Ctrl + Shift + O , you'll notice that we still have a compile error. We expected that, because we haven't written our code yet! Step 2 of TDD is to write your stubs to get it to compile. Fortunately, Eclipse's Quick Fix can help us generate our method stubs. With your text cursor on the makeAdmin method, hit Ctrl + 1 (the number one). You'll see an option to generate a method.

Eclipse figured out that we needed a void method, with three Strings. Pretty cool, huh? Rename the variables to be more meaningful. Like this:

Your unit test should now compile. Run it. Red bar!

Note that this is what failing expectations in EasyMock look like. It expected three calls, it got none.

On to Step 3 of TDD. Fill in the functionality of the makeAdmin call, based on our tests and the previous description.

When that new functionality is done, green bar!

Now let's say that the makeAdmin method needs a little bit of refactoring. Place the text cursor over the declaration of makeAdmin . Go to Refactor > Change Method Signature... Instead of the order of parameters being ( adminUser, password, userToBeAdmin ) , change the order to: ( userToBeAdmin, adminUser, password ) . Like this:

Hit Ok and re-run your unit tests. Green bar!

Exercise

Now it's time to attempt TDD with mock objects on your own. You have four separate tasks, in increasingly difficult order:

Submission & Grading

Due 4-2. Zip up your Eclipse project and submit it to the dropbox on myCourses. This activity is worth 30 points and is graded individually. Grading breakdown is as follows: