So, we've learned how to create some automated tests.
You've seen how to create positive tests, and how to use our inputs and assertions to verify things automatically. We've seen how to do negative tests, especially negative tests that involve throwing exceptions and handling those.
And along the way, to make ourselves independent and self-sufficient, we've always put everything necessary for that particular test within the method. This is a very good practice but as you can see it can get to be verbose. Now remember, being verbose is not a huge problem, because it is okay to repeat yourself when necessary.
However, frameworks such as TestNG and JUnit, all provide mechanisms for allowing us to help with some of the repetition around setting up test data and tearing it down.
Let's look at some of those.
Recall that when we defined a scenario, we had to have our customer, and we needed a savings account. And we left a test method up here:
/**
* Customers should be able to withdraw from their savings account.
* Scenario:
* 1. Given a customer's savings account with an initial balance of $100.00
* 2. When I withdraw $60.00 from the account
* 3. Then the new account balance is $40.00
**/
@Test
public void withdrawingValidAmountFromSavingsAccount_DecreasesBalanceByAmount() {
}
A positive scenario for setting up our withdrawal for $100, and withdrawing $60, and just verifying the balance of $40 ... blank.
We're going to use this as an example of how to do some refactoring, and really reduce everything within this test class, and also make the implementation of this particular method quite succinct.
And so, let's start.
The first thing that we have to do is to define a customer attribute as part of the class. We can also define our savings account attribute at this level as well, because we're going to need these two things.
public class SavingsAccountTest {
Customer customer;
SavingsAccount savings;
@BeforeClass
public void oneTimeSetup() {
customer = new Customer ("Mickey Mouse", "Disneyland", "Mickey@disneyland.com");
}
@BeforeMethod
public void eachTimeSetup() {
savings = new SavingsAccount(customer, 100.00, 123456789);
}
/**
* Customers should be able to withdraw from their savings account.
* Scenario:
* 1. Given a customer's savings account with an initial balance of $100.00
* 2. When I withdraw $60.00 from the account
* 3. Then the new account balance is $40.00
*/
And so, we see that when it comes to the customer attribute, we rarely changed it. We just defined it once, and it never really was modified, we just needed to have an owner for this account.
And so here, those types of things are very useful to place them in a “one-time setup” — oneTimeSetup
Anything that's a heavy operation or just happens once and doesn't really change is a good candidate to just happen once when the classes initialize. So here, we're going to define a oneTimeSetup
method.
We're going to place our initialization of our customer within that method. And we have to make sure that we don't redefine it, we're just referring to that customer. So that's that. Now we don't need that line from here.
But we still have our savings account. Now the savings account is a little trickier, because this does change, this balance especially.** **When we do withdrawals and deposits, we expect this to change, and so we need to make sure that before each method runs, that we have that value there.
One quick way to do that is to use a @BeforeMethod
- eachTimeSetup
.
And so here in a similar fashion to the oneTimeSetup
, we can define this. And now we want to refer it to our savings account and initialize it before each test is run. Again, we're not redefining it here, we're just initializing it.
And so now, in terms of this, our Given
has all floated up into these two methods.
And so now defining this scenario.
@Test
public void withdrawingValidAmountFromSavingsAccount_DecreasesBalanceByAmount() throws InsufficientFundsException {
// When
savings.withdraw(60.00);
// Then
assertEquals(savings.getBalance(), 40.00);
}
Well, we already have a customer's savings account that has an initial balance of $100, so we just need to do — savings.withdraw(60.00)
.
And then we can now assertEquals(savings.getBalance(), 40.00)
Now remember, withdrawal will throw an exception, so we need to make sure we add that exception clause to the method body.
And so here we can see that we've reduced, this is here or when, and this is our event.
So now we still have relatively independent methods, but there is a common set of Setup
and so we have to be conscious of that.
Now let's run this to make sure that our tests still pass. And there we go.
We've done a refactoring to help to reduce some of the effort when it comes to setting up data.
In a similar manner, if we wanted to tear down data, we can also use a common OneTimeTearDown
and EachTimeTearDown
method.
Quiz
The quiz for Chapter 5 can be found at the end of Section 5.4