Menu

Coding Rules

Anonymous

Test-driven development (TDD)

"Test-driven development is a programming technique that requires you to write actual code and automated test code simultaneously.
This ensures that you test your code—and enables you to retest your code quickly and easily, since it’s automated." (1)

How TDD works

Test-driven development(TDD) revolves around a short iterative development cycle that goes something like this:

  1. Before writing any code, you must first write an automated test for your code. While writing the automated tests, you must take into account all possible inputs, errors, and outputs.
  2. The first time you run your automated test, the test must fail assuring that the code is not yet ready.
  3. Afterward, you can write the actual code. Since there’s already an automated test, as long as the code fails it, it means that it is still not ready. The code must pass all tested assertions.
  4. Once the code passes the test, you can begin cleaning it up, via refactoring. As long as the code still passes the test, it means that it still works. You no longer have to worry about changes that introduce new bugs.
  5. Start the whole thing with the next feature.

Now let's go over this in more detail.

When you get a coding task, there should always be spelled out in some detail a couple of features. For example, the task could specify a function that returns something, and should throw an exception on invalid input. These would be two features each of which you implement separately using this workflow. Once you start breaking the coding task into such small sections of around 5 minutes, it should be obvious what test to write.

You then implement the bare minimum of code to make the test compile and fail. For example, your function might return null, or not throw the exception. While this might seem overly paranoid, it protects you to some extend against wrong tests. These things are rare, but take extra long to debug; after all, the code passed all tests, so the bug is surely somewhere else, isn't it? We want to point out that this is not an academic issue, the only real bug in our second release came with a buggy test (and took ages to find, because it was weird, like most bugs are)!

When the test fails, you just write the minimum amount of code that is required to pass the test. The key here is simplicity, "the art of maximizing the amount of work not done" (2). If you need some feature or know that you will eventually generalize a concept, you will rewrite the code later, in some cases much later (years); until then your might-be-needed code clutters up the code base.

When you have managed to implement the code to pass the test, you should refactor the code. That is, you should look at your code, and think whether you would print it on your CV. If several tests require the same setup, you should combine this setup code into a setup function. If you find some loop awkward or complicated to understand, make it more transparent. If you can put "b = a.get(); b.doSomething()" into a single command "a.get().doSomething()", feel free to do so. Add javadoc, and put comments where necessary. In short, make your code not only functional, but beautiful. Since you have written the test already, you can change the code at your heart's desire, the test will catch bugs introduced during refactoring.

If, despite all precautions, a bug is found later, there have obviously been tests missing. That is why bugfixes should always come with a test for the bug.

Why bother with TDD

You might wonder why you should waste your time with all these additional tests. To give you a selection of reasons:

  • Your code is (almost) guaranteed to be bug-free. If you have ever seriously hunted bugs, you will know that this takes ages. Essentially what you do in the end is testing everything by hand. This takes approximately as long as writing the test in the first place, but has an additional disadvantage: The next time you change your debugged code and introduce another bug, you have to start testing by hand again...
  • For this reason, everyone agrees that unit tests are great, but spending the day writing only unit tests is boring, so you will try to avoid it. By making the writing of the tests part of the coding, they are done automatically.
  • You get an immediate sense of achievement. You have done five minutes of coding, implemented some small piece of code, written the corresponding test, and now you can compile and run a working program (the test). This can be extremely motivating. Compare this to the normal way of hacking away alone on some subsystem, where you typically figure out after a week of coding that your code has a bug, and you have to go through all the code and debug it again without having anything to show.

Some other reasons that are not so relevant to our situation, since we usually have well-defined coding tasks:

  • You do not only write the code, but immediately use it as well. So if you write a class with some functions that is somehow awkward to use, you will notice immediately, because your test feels ugly. Also, you automatically disentangle your code. If you want to test class A, but first need to instantiate classes B, C, D, and E with special settings, you will pretty quickly notice that you should rethink your design, because the test is just so tiresome to write.
  • You automatically break your work down into small pieces by thinking about which tests you need. Instead of being demotivated by a vague, but big todo list, you end up with many small items that are easy to solve on their own, and marvel about how you cross out one item after the other (see the sense of achievement item above).

Implications on Design/Coding

Closely connected with the test-driven design is the use of the SOLID principle. In addition and as a consequence, there are some common design patterns that make testing and extension easier. To name the most important ones:

  • Dependency Injection: If some object requires an instance of some class/interface (a dependency) for proper functioning, you always pass this dependency, never create it inside the object. As an example, the ScenarioBuilder, which loads scenarios, needs a ResourceRepository, i.e., a kind of list of available scenario files. This repository is injected in the constructor. Using this pattern has several advantages:
    • You centralize the code where objects are created in some builders instead of scattering it around the code base. In our case, ScenarioBuilder is completely decoupled from any details of how a ResourceRepository is set up (or even what implementation of this interface we use); it just uses some generic features.
    • It is very easy to swap one repository by another one. If in the course of the development, we modify the repository code, or introduce different repositories for different usages, we do not have to touch the ScenarioBuilder code.
  • Mock classes: If you consequently use dependency injection to pass all dependencies, you can create mock/fake objects for testing. These allow you to check in a simple way if a certain function of an injected object has been called, or you can check for proper error handling. For the ScenarioBuilder example, you can check that it indeed the repository was queried for the correct files. Or you can make your mock repository throw a FileNotFoundException to check in a controlled way how the ScenarioBuilder will react.
  • Factory pattern: Sometimes, you cannot directly inject an object, but need to create it on the fly. In this case, you encapsulate the creation in a factory/builder. Again, for our example, the ScenarioBuilder needs to do some post-processing after loading the scenario. However, the objects that do the post-processing can only be created after the scenario was loaded. The solution is to create and inject a factory that returns the objects for the post-processing. Advantages of the factory pattern:
    • It keeps the object creation code centralized. When you need to change how a certain important object is created, you know immediately where to look.
    • You keep the advantages of dependency injection, and have the ability to inject mock classes for a detailed testing of the behavior and the exception handling.

Resources

To understand TDD we think that these two books are very interesting and useful:

  • Growing Object-Oriented Software Guided by Tests (Addison Wesley)
  • Test-driven development: by example (Addison Wesley)

If someone is also interested in coding in an elegant way(very appreciated here) and wants to learn how to code a good clean code these lectures can do the trick:

  • Clean Code: A Handbook of Agile Software Craftsmanship (Prentice Hall)
  • Agile Software Development, Principles, Patterns, and Practices (Prentice Hall)

Here instead some useful and easy to understand links that talks in general and in a easy way about good programming practices (even if principles are better exposed in the previous books):

  1. http://agilesoftwaredevelopment.com/videos/test-driven-development-basic-tutorial (very interesting video tutorial that shows how to use eclipse to code in a TDD way on a very simple code)
  2. http://www.davesquared.net/2009/01/introduction-to-solid-principles-of-oo.html
  3. http://www.garshol.priv.no/blog/105.html
  4. http://net.tutsplus.com/tutorials/html-css-techniques/top-15-best-practices-for-writing-super-readable-code/

Other coding rules

Apart from using TDD, there are a handful of further rules:

  • Code checkins should always be done in a separate branch, where a branch lives for about 1-2 chat sessions. Commits to the master branch may only be done by a reviewer as part of the reviewing. This should fix the all too common problem that new commits break tests or compilation, hindering everyone else.
  • When you have edited a file the first time this year, always add your name with the appropriate year at the comment header. When you add a new file, put the standard GNU General Public License header on top. By doing so, you acknowledge that you place your code under the license.
  • Indentation is done by tabs, not by spaces.
  • Sometimes, your tests will have to fake interfaces to test if some class uses these interfaces correctly, and JMock just does not quite handle what you want to do.
    • The resulting fake classes should have the name "Fake" somewhere in their class name
    • They should be put into separate packages. If the test is in package xxx, then the fake class should generally be located in xxx.fake.
    • If you just want to test that a certain function is called or so, the prefered way is the use of JMock.
  • There are a handful of cases where you do not need tests. These are:
    • Primitive getters and setters (which just return/set a private variable)
    • equals() and hashCode() functions if they are auto-generated by your development environment.
    • external libraries
  • If your tests need additional files, put them in the respective package into a "files" subdirectory
  • Code should be commented. See the next section on the details

Commenting and documenting

Code should always be commented via Javadoc. If you are new to Javadoc, you can find the official reference on Sun's webpage (ignore the command line arguments part, you should not have to deal with that yourself). Note that there is an ant-target "build-docs" for building the whole javadoc documentation from the sources. IDE's probably have similar features.

There are a couple of rules for commenting. To see them in example, have a look at the org.freelords.resourcemanager package. This is one of the earliest and least hacked packages; while it might have things lacking, it should generally follow and visualize these rules.

  • Every (public) function should have a documentation header that has at least a short summary and describes the input parameters, return value, and thrown exceptions
    • If the function does something special or there are good and not easily visible reasons why it is designed the way it was designed, this should also be noted in the documentation.
    • Trivial functions, where a short glance tells you what they do, are an exception; they can be documented by a one-phrase javadoc comment.
    • For derived functions, make use of the @inheritDoc macro
  • Every class should have a javadoc documentation header. The amount of information there should reflect how difficult it is to understand the class.
    • For trivial classes, e.g., an enumeration, a single sentence is enough.
    • For more complex classes, explain briefly in a stand-alone way what they do, and how they are used.
    • If classes interact in a complex way, describe this, or at least point to more exhaustive documentation (e.g., package documentation)
  • Every package should be documented. The documentation for a package is found in a file "package.html" in the package directory.
    • Exceptions are rather obscure packages that are not required for an understanding of the general code flow, e.g. army.jaxb, which only deals with some obscure details of XML unmarshalling.
    • The package documentation should give an overview how the classes in the package interact.
    • It should also explain briefly the function and interaction of the subpackages, so that the documentation in the subpackages can focus on the details of this specific package.

Related

Wiki: Home
Wiki: Newcomers