Good test, bad test.

A good test:

  • verifies one thing. note: this is not equal to “has one assert only”. a “thing” can be a “user is updated”, which may require several asserts for individual fields.
  • this “one thing” is clearly stated in the test name
  • does not perform any other duplicate verification (e.g. if there is a test verifying that a media can be inserted, then no other tests should have the same check after they insert media for test purposes)
  • creates any data it needs to operate. No reliance on some magic data previously created by someone manually.
  • on exit — deletes any data it created (in “finally” block).
  • does not interfere with other tests that are running in parallel in the same JVM
  • does not interfere with another copy of the SAME test running in parallel (in another JVM) — keep this in mind when creating integration tests: many copies of the same test can be running at the same time in separate JVMs. you don’t want one instance creating data in a database and another instance deleting that data at the same time. this typically requires generating some unique data IDs or something.
  • uses realistic setup (beware of danger of mocking out too much of your system — you end up testing your mocks rather than actual code). I have seen this more than once…
  • is less than ~15 lines long (for the sake of readability, plus this is simply a natural side-effect of the requirements listed above).
  • does not rely on any particular tests execution order. see examples below how to split long “happy path” test into multiple test methods each verifying one thing.
  • is deterministic: gives the same result 100 times out of 100. if it works 99 times out of 100, delete it right away and write another one that is deterministic. note on integration tests: this assumes that the external system stays “the same”. writing integration tests against an external system will always be a set of compromises, but can be alleviated by using Stub objects — this is where “programming against interfaces” comes to play.
  • bonus points: uses fest-style asserts (see “assertThat” syntax from AssertJ library) rather than old JUnit style “assertEquals”.
  • follows the same level of code quality standards that are applicable to production code

This was about individual tests.

Now on tests in general:

  • They need to verify many important paths, not just “happy path”
  • Need to use realistic setup. E.g. if you have a webapp with REST endpoints deployed to Tomcat then launching embedded tests in Jetty would be verifying that your tests-constructed Jetty setup works. Which has little to do with your real production setup that you ACTUALLY need to test.

Now some examples of what I consider “good” and “bad” tests.

Good (*) test:

* except for hardcoded location id that I would suggest replacing with generating some random one.

@Test
public void locationIsInserted() throws Exception {
final String locationId = "123";
final String ownerId = "234";
try {
LocationVerifier.verifyLocationDoNotExist(locationId);
final Location location = LocationGenerator.generateForLocationId(locationId);
DB.insertLocation(ownerId, location);
final Location found = DB.findLocation(locationId);
assertThat(found.getFlag())
.isEqualTo(location.getFlag());
} finally {
Cleaner.deleteLocation(locationId);
}
}

Bad test:

Posted some sample code. Looked at it. Cried for a few minutes. Deleted it.