Checking Sanity in TDD Testing
Whether you’re in the process of fully adopting Test Driven Development (TDD), providing unit tests after you have written your code, or something in between, the question that you will soon ask is: “What should I be testing?”
The simplest answer to me is: any place where you can isolate and test just your code.
In my opinion, Test-Driven Development testing and unit testing differ from Behavior-Driven Development testing in the scope of what is tested. For instance, let’s say I’m building a system to track information about a novel I’m writing. Among other services, I would need would be a way to create, read, update, and delete characters I want to use in my novel.
In BDD-style testing, or any type of integration testing, I want to get the full behavior of a process – for instance, saving a character. This would test the call to the service, any validation that is required, a call to the data layer (or a DAO), and possibly even an insert into a database.
In TDD-style testing, or unit tests, I am interested in a more narrow scope – for instance, just the validation. This is where my business logic resides, and where I’m writing my code.
Where not to test?
Before we get too far down that road, let’s also talk about where we do not want to test: any place where we are not actually doing any logic, such as getters and setters.
Code such as:
public void setName(String name) { this.name = name; }
It provides no real business logic. You can see by looking at the code that it only sets the variable name. Should you have to break away from convention and provide logic in the setting, it still would not be the place to create a test.
For instance:
public void setName(String name) { if(!validate(name)) { throws new Exception(“invalid name”); } this.name = name; }
In this case, I would write a test on the validate method, but I still would not need one on the setter.
I would also not write any code that tests someone else’s code. In most modern database frameworks, the framework provides most of the methods to save an object to the database. The code we have to write to accomplish that is usually not more than instantiating, or even accessing an already-instantiated connection to the database, and call save. Any test written against this code would simply be testing code provided by the framework, which I would hope has already been well tested. If you mock the framework, then you are essentially testing the mocking process.
For instance, it is possible that a save function could have no more in it than:
public CharacterEntity save(CharacterEntity character) { if(character != null) { session.save(character); session.refresh(character); } return character; }
The main logic exists in save and refresh, which was written for me by the good people who maintain Hibernate.
Finally, in most cases, the user interface layer would not benefit from this level of unit testing. In most cases, what I need to test from a UI point of view is the look and feel of the application. Actually viewing the application is the best way to test that part.
Should you have to put logic in your UI layer, there are test frameworks that can help you, such as JSF Unit for JSF testing. I would suggest, as much as you can, to keep your business logic out of the UI layer.
Where to test?
So, we have narrowed the test code down to testing just the code that provides the logic to make the process work.
In our our Character service example, the save method might simply validate the Character and then call the DAO. In this example, the validate method is an important method to test.
public class CharacterService { private CharacterValidation validator; private CharacterDAO dao; … public void save(CharacterEntity character) throws BookWriterException { if (!validator.validate(character)) { throw new BookWriterException(); } dao.save(character); } … }
To write a test for this method, we would want to mock both validator and DAO. This would not provide us with a meaningful test. But the CharacterValidation object is where we have all our business logic for saving a Character.
For instance, we must have an object to work with, so our first test would be what would happen if you pass in a null Character:
@Test public void testValidate_Null() { CharacterEntity character = null; CharacterValidation instance = new CharacterValidation(); boolean result = instance.validate(character); assertFalse(result); }
The object should return a false. Next our business requirements state that each Character has to have a name:
@Test public void testValidate_EmtpyName() { CharacterEntity character = new CharacterEntity(); CharacterValidation instance = new CharacterValidation(); boolean result = instance.validate(character); assertFalse(result); }
That would provide a test to make sure we have a check for name. As we work thorough the requirements for this service, we can write test that will check each of them.
Final Thoughts
This provides me with a sanity check. When I write an application without any tests, I have to write more of it before I can run it and see if it works. Using our Character service example, I can write just the validator and test it, before I have to write other code.
Otherwise, along with testing this code, I’m testing the integration with all the other parts of the system. If something goes wrong, and I don’t see the result I expect, having written the test narrows down the culprit.
Reference: | Checking Sanity in TDD Testing from our JCG partner Rik Scarborough at the Keyhole Software blog. |