Reputation: 750
I am just learning JUnit testing procedures and have gone through all the tutorials I could find. I understand the concept of implementing basic tests, such as when a method takes a couple of inputs and uses them to compute a return value. In that case, it is simple to write a test asserting what the expected return value should be.
However, what does one do when there is a more complex method to test, such as perhaps one that uses numbers generated from a Random object, or if one needs to test a method that does not return a value?
For instance, I have the following method for which I would like to write a test:
// Requirement 9.5.0
public void firePhotonTorpedos(TrekGUI gui, Starship target, int score)
{
// Requirement 9.5.1.1
// Torpedoes can either hit or miss.
Random rand = new Random();
boolean hit = false;
int isItAHit= rand.nextInt(10);;
int damage = 0;
//80 % chance of hitting target
if(isItAHit < 8)
{
hit = true;
}
// if it was a hit, calculate damage
if(hit)
{
//damage between 30 and 50
damage = rand.nextInt(21) + 30;
target.setHitPoints(target.getHitPoints() - damage);
}
this.setPhotonTorpedos(this.getPhotonTorpedos() - 1);
// Requirement 5.4.1
this.setEnergy(this.getEnergy() - 50);
//if target hit but not destroyed, say so
if(hit && target.getHitPoints() > 0)
{
gui.getBottomPanel().getConsole().append("\nVessel at " + target.getQuadrantLocation() + ", " + target.getSectorLocation() + " damaged.");
gui.getBottomPanel().getConsole().append("\n" + damage + " units damage.");
}
//if target destroyed, say so and remove its ship
// Requirement 6.2.0
else if(hit && target.getHitPoints() < 0)
{
//warship is gone, so set its contains field to false and remove its icon
target.getSectorLocation().setContainsWarship(false);
target.getSectorLocation().getSectorView().hideWarshipIcon();
klingonsLeft = klingonsLeft - 1;
gui.getBottomPanel().getConsole().append("\n" + damage + " units damage.");
gui.getBottomPanel().getConsole().append("Vessel at " + target.getQuadrantLocation() + ", " + target.getSectorLocation() + " destroyed.");
// Increase score (bonus depending on how much energy is left)
int bonus = this.getEnergy()/20;
score = score + (50 + bonus);
}
//if it was a miss, say so
else
{
gui.getBottomPanel().getConsole().append("Torpedo missed.");
}
}
How would I write a formal test for this? Since I have three predicate statements in the code, I know I should have three separate paths through the code:
fire torpedo > hit (no) > end
fire torpedo > hit (yes) > calculate damage > destroyed (no) > vessel remains > end
fire torpedo > hit (yes) > calculate damage > destroyed (yes) > vessel gone > end
So if I want to get complete path coverage, for instance, I need to write at least three test cases in which these conditions are expressed. It is easy to see what those values could be. But how do I use them to write a test? Can JUnit access the boolean hit value in my code, for example, so that I can define the value without using a random number? I know I could just step through the code to see if it is working (and I have), but I have been told that I should use more formal testing means like JUnit instead.
I hope these questions make sense. I am just hoping to get a better sense of how this sort of testing is done in real world scenarios.
Upvotes: 0
Views: 3400
Reputation: 1504182
However, what does one do when there is a more complex method to test, such as perhaps one that uses numbers generated from a Random object
You find a way of controlling that Random
. For example, you could pass it into the method, and have a parameterless overload which calls it with a new Random()
if you really want to.
That way your tests can pass in whatever Random
instance they want - e.g. one with a preset seed, or one which uses a mocking framework to allow you to control the values returned.
or if one needs to test a method that does not return a value?
You test the side-effects. Your method must have side-effects, or it's pointless. You just need a way of accessing those side-effects (e.g. by calling target.getHitPoints()
.
Now currently your method seems to have "model" side-effects and UI side-effects... and it's also quite long. Both of those will make it hard to test. Splitting your code up into smaller, more focused methods generally makes it easier to read and easier to test. Separating your UI from your business logic helps in that respect, too.
Upvotes: 3