This document contains information for an outdated version (2.4) and may not be maintained any more. If some of your projects still use this version, consider upgrading as soon as possible.
Why Unit Test?
Note: This is part of the SilverStripe Testing Guide.
So at this point, you might be thinking, "that's way too complicated, I don't have time to write unit tests on top of all the things I'm already doing". Fair enough. But, maybe you're already doing things that are close to unit testing without realizing it. Everyone tests all the time, in various ways. Even if you're just refreshing a URL in a browser to review the context of your changes, you're testing!
First, ask yourself how much time you're already spending debugging your code. Are you inserting
die statements into different parts of your program and watching the details dumping out to screen? Yes, you
know you are. So how much time do you spend doing this? How much of your development cycle is dependent on dumping out
the contents of variables to confirm your assumptions about what they contain?
From this position, it may seem that unit testing may take longer and have uncertain outcomes simply because it involves adding more code. You'd be right, in the sense that we should be striving to write as little code as possible on projects. The more code there is, the more room there is for bugs, the harder it is to maintain. There's absolutely no doubt about that. But if you're dumping the contents of variables out to the screen, you are already making assertions about your code. All unit testing does is separate these assertions into separate runnable blocks of code, rather than have them scattered inline with your actual program logic.
The practical and immediate advantages of unit testing are twofold. Firstly, they mean you don't have to mix your debugging and analysis code in with your actual program code (with the need to delete, or comment it out once you're done). Secondly, they give you a way to capture the questions you ask about your code while you're writing it, and the ability to run those questions over and over again, with no overhead or interference from other parts of the system.
Unit testing becomes particularly useful when exploring boundary conditions or edge case behavior of your code. You can write assertions that verify examples of how your methods will be called, and verify that they always return the right results each time. If you make changes that have the potential to break these expected results, running the unit tests over and over again will give you immediate feedback of any regressions.
Unit tests also function as specifications. They are a sure way to describe an API and how it works by simply running the code and demonstrating what parameters each method call expects and each method call returns. You could think of it as live API documentation that provides real-time information about how the code works.
Unit test assertions are best understood as pass/fail statements about the behavior of your code. Ideally, you want every assertion to pass, and this is usually up by the visual metaphor of green/red signals. When things are all green, it's all good. Red indicates failure, and provides a direct warning that you need to fix or change your code.
Everyone has a different set of ideas about what makes good code, and particular preferences towards a certain style of logic. At the same time, frameworks and programming languages provide clear conventions and design idioms that guide code towards a certain common style.
If all this ranting and raving about the importance of testing hasn't made got you thinking that you want to write tests then we haven't done our job well enough! But the key question still remains - "where do I start?".
To turn the key in the lock and answer this question, we need to look at how automated testing fits into the different aspects of the SilverStripe platform. There are some significant differences in goals and focus between different layers of the system and interactions between the core, and various supporting modules.
In open source core development, we are focussing on a large and (for the most part) stable system with existing well defined behavior. Our overarching goal is that we do not want to break or change this existing behavior, but at the same time we want to extend and improve it.
Testing the Sapphire framework should focus on characterization. We should be writing tests that illustrate the way that the API works, feeding commonly used methods with a range of inputs and states and verifying that these methods respond with clear and predictable results.
Especially important is documenting and straighten out edge case behavior, by pushing various objects into corners and twisting them into situations that we know are likely to manifest with the framework in the large.
Modules usually encapsulate a smaller, and well defined subset of behavior or special features added on top of the core platform. A well constructed module will contain a reference suite of unit tests that documents and verifies all the basic aspects of the module design. See also: modules.
Testing focus on client projects will not be quite so straightforward. Every project involves different personalities, goals, and priorities, and most of the time, there is simply not enough time or resources to exhaustively predicate every granular aspect of an application.
On application projects, the best option is to keep tests lean and agile. Most useful is a focus on experimentation and prototyping, using the testing framework to explore solution spaces and bounce new code up into a state where we can be happy that it works the way we want it to.
Rules of Thumb
Be aware of breaking existing behavior. Run your full suite of tests every time you do a commit.
Not everything is permanent. If a test is no longer relevant, delete it from the repository.