转载:http://ericanderson.us/2008/05/08/using-eclemma-to-write-better-unit-tests/
成都创新互联公司云计算的互联网服务提供商,拥有超过13年的服务器租用、遂宁服务器托管、云服务器、网站空间、网站系统开发经验,已先后获得国家工业和信息化部颁发的互联网数据中心业务许可证。专业提供云主机、网站空间、申请域名、VPS主机、云服务器、香港云服务器、免备案服务器等。
If you don’t already write unit tests you should be. (Hey, why aren’t you writing unit tests?) Unit testing has so many benefits and the upfront developer cost to write some unit tests can pay huge dividends later when you aren’t spending time debugging broken code nor attempting to save face due to clueless mistakes. You can also use them as a contract to the expectations of your implementations.
[@more@]So, assuming you have some unit tests, how useful are they if they don’t test everything? At some level you will want to have a good idea that you’re testing everything. (NOTE: I mean everything really important. Writing perfect 100% coverage like this would likely be too expensive for the entire codebase.) This is where Emma comes in (and more importantly for us, EclEmma, an Eclipse plugin for Emma). Emma is a code coverage tool which lets you visual which parts of your code get executed during some execution (regular or JUnit).
Lets walk through using EclEmma to ensure that we have adequate testing being done. Lets test the following two classes.
GuessTheNumber.java:
01.
import
java.util.Random;
02.
03.
public
class
GuessTheNumber {
04.
05.
private
final
Integer value;
06.
private
final
int
min;
07.
private
final
int
max;
08.
private
int
guesses =
0
;
09.
private
boolean
solved =
false
;
10.
11.
public
GuessTheNumber(
int
min,
int
max,
int
value) {
12.
this
.min = min;
13.
this
.max = max;
14.
this
.value = value;
15.
}
16.
17.
public
GuessTheNumber(
int
min,
int
max) {
18.
this
(min, max, getRandomNumber(min, max));
19.
20.
}
21.
22.
public
int
guess(
int
i) {
23.
if
(solved)
24.
throw
new
IllegalStateException();
25.
26.
guesses++;
27.
28.
int
ret = value.compareTo(i);
29.
30.
if
(ret ==
0
)
31.
solved =
true
;
32.
33.
return
ret;
34.
}
35.
36.
public
void
resetCount() {
37.
guesses =
0
;
38.
solved =
false
;
39.
}
40.
41.
public
int
getValue() {
42.
if
(solved ==
false
)
43.
throw
new
IllegalStateException();
44.
45.
return
value;
46.
}
47.
48.
public
int
getMin() {
return
min; }
49.
public
int
getMax() {
return
max; }
50.
51.
private
static
int
getRandomNumber(
int
min,
int
max) {
52.
return
new
Random().nextInt(max - min +
1
) + min;
53.
}
54.
}
And SequentialStrategy.java:
01.
public
class
SequentialStrategy {
02.
03.
private
final
GuessTheNumber guesser;
04.
05.
public
SequentialStrategy(GuessTheNumber guesser) {
06.
this
.guesser = guesser;
07.
}
08.
09.
public
int
solve() {
10.
for
(
int
i=guesser.getMin(); i< =guesser.getMax(); i++) {
11.
if
(guesser.guess(i) ==
0
)
12.
return
i;
13.
}
14.
15.
throw
new
IllegalStateException();
16.
}
17.
}
Finally, we’ll write up a basic StrategyTest.java:
01.
import
junit.framework.TestCase;
02.
03.
public
class
StrategyTest
extends
TestCase {
04.
05.
public
void
testSequentialStrategy() {
06.
GuessTheNumber guesser =
new
GuessTheNumber(
5
,
100
);
07.
SequentialStrategy strategy =
new
SequentialStrategy(guesser);
08.
09.
int
value = strategy.solve();
10.
assertEquals(guesser.getValue(), value);
11.
}
12.
}
So the question is, how good is our test? If you installed EclEmma, you can right click on your Unit Test in Eclipse, head to “Coverage As” -> “JUnit Test”
This will run your unit test against your code and when complete, highlight the code green, yellow, or red for covered, partially-covered, and not-covered respectively.
According to EclEmma, our code coverage when running StrategyTest is:
Thats not too bad, lets take a look at the output for SequentialStrategy:
We may want to test the IllegalStateException since we currently don’t and we are expecting it to happen if we’ve guessed everything between min and max and haven’t solved the problem. This would ensure if someone else comes in behind us and changes this code, the unit test will fail if they take that out and change it to return, say, Integer.MIN_VALUE.
Lets also take a look at some of GuessTheNumber:
It appears we also want the contract to include an IllegalStateException being thrown if you have already solved the guessing game. It also looks like we don’t ever call resetCount() and possibly retest after doing that. We also never check if getValue() fails prior to having a solution.
If we modify the test slightly to:
01.
import
junit.framework.TestCase;
02.
03.
public
class
StrategyTest
extends
TestCase {
04.
05.
public
void
testSequentialStrategy() {
06.
GuessTheNumber guesser =
new
GuessTheNumber(
5
,
100
);
07.
SequentialStrategy strategy =
new
SequentialStrategy(guesser);
08.
09.
for
(
int
i=
0
; i<
3
; i++) {
10.
try
{
11.
guesser.getValue();
12.
fail(
"An exception should have been thrown"
);
13.
}
14.
catch
(IllegalStateException ex) {
/**/
}
15.
16.
int
value = strategy.solve();
17.
assertEquals(guesser.getValue(), value);
18.
19.
try
{
20.
guesser.guess(guesser.getMax() +
1
);
21.
fail(
"Shouldn't reach this point"
);
22.
}
23.
catch
(IllegalStateException ex) {
/**/
}
24.
25.
guesser.resetCount();
26.
}
27.
}
28.
29.
public
void
testBadGuessGame() {
30.
GuessTheNumber guesser =
new
GuessTheNumber(
1
,
10
,
3000
);
31.
SequentialStrategy strategy =
new
SequentialStrategy(guesser);
32.
33.
try
{
34.
strategy.solve();
35.
fail(
"Should not reach this point"
);
36.
}
37.
catch
(IllegalStateException ex) {
/**/
}
38.
}
39.
}
And now our code coverage is:
I know this was a very basic example that really didn’t test any complex logic or conditions, but I hope it gives you a good idea of how you can use code coverage to improve your unit tests.