I've said before, and I'll say again, I'm not a fan of Test Driven Development, tests and test frameworks have their place, but they should not; in my opinion; be the driving force behind a projects development stream - even if it does give managers above the dev team a warm fuzzy sense of security, or if it allows blame to be appropriated later, you're a team, work as a team and use tests on a per-developer basis as a tool not as a business rule.
*cough* I do go off topic at the start of posts don't I... *heerrhum*, right... Top Three Automated Testing Tips... From my years of experience...
1. Do not test items which are tested by masses of other developers... I'm talking about when you're using a frame work of library, ensure you are using it correctly certainly, do this at training or with your coding standard, but then do not labour the point by re-testing... Lets take a good example of this, the C++ Standard Library.
The Standard Library contains many collection classes, these classes have iterators within, lets look at a vector:
#include <vector>
std::vector<int> g_SomeNumbers { 1, 3, 5, 7, 9 };
We could iterate over the collection and output it thus:
int g_Sum(0);
for (int i (0); i < g_SomeNumbers.size(); ++i)
{
g_Sum += g_SomeNumbers[i];
}
However, this is not leveraging the STL properly, you are introducing the need to test the start poing "int i(0);" the end condition "i < g_SomeNumbers.size();" and the iterator "++i", three tests, slowing your system down and complicating your code base.
int g_Sum(0);
-- TEST SUM START
-- TEST i COUNT START
-- TEST RANGE CONDITION LIMIT
for (int i (0); i < g_SomeNumbers.size(); ++i)
{
-- TEST ITERATION
g_Sum += g_SomeNumbers[i];
-- TEST SUM CALCULATION - THE ACTUAL WORK DONE
}
-- REPORT TESTS
Using the iterator, we leverage all the testing of the STL, we remove the need to range test the count variable, we remove the need to test the condition and leave only the step as a test to carry out...
int g_Sum(0);
for (auto i(g_SomeNumbers.cbegin()); i != g_SomeNumbers.cend(); ++i)
{
g_Sum += (*i);
}
Our code looks a little more alien to oldé timé programmers however, it's far more robust and requires less tests simply because we can trust the STL implementation, if we could not thousands, hundreds of thousand of developers with billions of other lines of code would have noticed the issue, our trivial tests show nothing of gain, so long as we've written the code to a standard which uses the interface correctly...
int g_Sum(0);
-- TEST SUM START
for (auto i(g_SomeNumbers.cbegin()); i != g_SomeNumbers.cend(); ++i)
{
-- TEST ITERATION
g_Sum += (*i);
-- TEST SUM CALCULATION - THE ACTUAL WORK DONE
}
-- REPORT TESTS
2. Do now allow values which have been tested to change unexpectedly... I'm of course talking about "const", which I have covered before on these pages, but constness in programming is key. The C family of languages allow constness at the variable level, you may notice in the previous point I used a const iterator (with cbegin and cend) as I do not want the loop to change the values within the vector... Constness removes, utterly, the need to perform any tests upon the integrity of your data.
If it's constant, if the access to it is constant, you do not need to test for mutations of the values.
Your coding standard, automated scripts upon source control submissions, and peer review are your key allies in maintaining this discipline, however it's roots stretch back into the system design and anlysis stages of the project, to before code was cut, when you were discussing and layout out the development pathway, you should identify your data consider it constant, lock it down, and only write code allowing access to mutable references of it as and when necessary.
Removing the need to retest mutable calls, removing the need to log when a mutable value is called because you trust the code is key.
In languages, such as python, which do not directly offer constness, you have to build it in, one convention is to declare members of classes with underscores to intimate they are members, I still prefer my "m_" for members and "c_" for constants, therefore my post-repository submit hooks run scripts which check for assigning to, or manipulation of "c_" variables. Very useful, but identified by the coding standard, enforced by peep review, and therefore removed from the burden of the test phase.
3. Remove foreign code from your base... I'm referring to code in another language, any scripting, any SQL for instance, anything which is not the pure language you are working within should be removed from the inline code.
This may mean a stored procedure to store the physical SQL, rather than inline queries throughout your code, it maybe the shifting of javascript functions to a separate file and their being imported within the header of an HTML page.
But it also includes the words we ourselves use, be that error messages, internationalisation, everything except code comments which is in whatever language you use (English, French etc etc) should be abstracted away and out of your code.
Your ways of working, coding standards, analysis and design have to take this into account, constness plays it's part as well, as does mutability, where ever you move this language to, and whatever form it takes test it a head of time, and then redact that test from your system level tests, trust you did it right based on the abstraction you've performed. Then avoid burdening your system throughout the remaining development cycle.
One could expand this to say "any in-house libraries you utilise, trust their testing" just as I stated with the STL in my first point, however, I am not talking about code, I am talking about things which are not code, which are uniquely humanly interpretable.
The advantage of removing them and pre-testing the access to them is that you retain one location at which you have an interlink, one place at which a value appears, one place where they all reside and so you leverage easily converting your programs language, you leverage easily correcting a spelling mistake, and all without needing to change your system code; perhaps without needing to even re-release or re-build the software itself (depending on how you link to the lingual elements)
Ultimately reducing the amount of testing required.