So I’ve read the official JUnit docs, which contain a plethora of examples, but (as with many things) I have Eclipse fired up and I am writing my first JUnit test, and I’m choking on some basic design/conceptual issues.
So if my WidgetUnitTest is testing a target called Widget, I assume I’ll need to create a fair number of Widgets to use throughout the test methods. Should I be constructing these Widgets in the WidgetUnitTest constructor, or in the setUp() method? Should there be a 1:1 ratio of Widgets to test methods, or do best practices dictate reusing Widgets as much as possible?
Finally, how much granularity should exist between asserts/fails and test methods? A purist might argue that 1-and-only-1 assertions should exist inside a test method, however under that paradigm, if Widget has a getter called getBuzz(), I’ll end up with 20 different test methods for getBuzz() with names like
@Test
public void testGetBuzzWhenFooIsNullAndFizzIsNonNegative() { ... }
As opposed to 1 method that tests a multitude of scenarios and hosts a multitude of assertions:
@Test
public void testGetBuzz() { ... }
Thanks for any insight from some JUnit maestros!
Pattern
Interesting question. First of all – my ultimate test pattern configured in IDE:
I am always starting with this code (smart people call it BDD).
In
givenI place test setup unique for each test.whenis ideally a single line – the thing you are testing.thenshould contain assertions.I am not a single assertion advocate, however you should test only single aspect of a behavior. For instance if the the method should return something and also has some side effects, create two tests with same
givenandwhensections.Also the test pattern includes
throws Exception. This is to handle annoying checked exceptions in Java. If you test some code that throws them, you won’t be bothered by the compiler. Of course if the test throws an exception it fails.Setup
Test setup is very important. On one hand it is reasonable to extract common code and place it in
setup()/@Beforemethod. However note that when reading a test (and readability is the biggest value in unit testing!) it is easy to miss setup code hanging somewhere at the beginning of the test case. So relevant test setup (for instance you can create widget in different ways) should go to test method, but infrastructure (setting up common mocks, starting embedded test database, etc.) should be extracted. Once again to improve readability.Also are you aware that JUnit creates new instance of test case class per each test? So even if you create your CUT (class under test) in the constructor, the constructor is called before each test. Kind of annoying.
Granularity
First name your test and think what use-case or functionality you want to test, never think in terms of:
shouldTurnOffEngineWhenOutOfFuel()is good,testEngine17()is bad.More on naming
What does the
testGetBuzzWhenFooIsNullAndFizzIsNonNegativename tell about the test? I know it tests something, but why? And don’t you think the details are too intimate? How about:It both describes the input in a meaningful manner and your intent (assuming disabled buzz is some sort of
buzzstatus/type). Also note we no longer hardcodegetBuzz()method name andnullcontract forFoo(instead we say: whenFoois not provided). What if you replacenullwith null object pattern in the future?Also don’t be afraid of 20 different test methods for
getBuzz(). Instead think of 20 different use cases you are testing. However if your test case class grows too big (since it is typically much larger than tested class), extract into several test cases. Once again:FooHappyPathTest,FooBogusInputandFooCornerCasesare good,Foo1TestandFoo2Testare bad.Readability
Strive for short and descriptive names. Few lines in
givenand few inthen. That’s it. Create builders and internal DSLs, extract methods, write custom matchers and assertions. The test should be even more readable than production code. Don’t over-mock.I find it useful to first write a series of empty well-named test case methods. Then I go back to the first one. If I still understand what was I suppose to test under what conditions, I implement the test building a class API in the meantime. Then I implement that API. Smart people call it TDD (see below).
Recommended reading: