I’d always learned that you should have your tester(s) independent from your coder(s). While that’s ideal, in a lot of places where I’ve worked, the budget and/or timeframe simply don’t allow it; I’m often the entire IT department around.
A great way to mitigate the lack of a second pair of eyes is to spend a few minutes during the design phase (AKA the “ok, so how am I going to pull this off?” phase) asking “how am I going to test this?” I’ve found many times, at least for myself, that coming up with some test cases is a forces me to consider situations that I wouldn’t have thought of when coding started. Sometimes it’s merely handling a boundary case, but sometimes it causes a significant redesign.
Example:
I was writing a program that would allow a telephone call center to update customer information once a month on a selected subset of customers that can change each month. The general idea was that I’d generate a series of unique IDs for each customer that needed updating that month and use that as a url:
https://www.example.com/CallCenter.php?GUID=12345678abcd
And that web page would look up the customer information and present the items for the data that we needed to collect.
However, a case I didn’t consider at the outset was “what do I do if that customer’s information is updated by another method between the time that I make the URL and the time the phone call is made?” However, I was putting together a list of the cases to test, and that was among them. In the situation we were in, making the calls costs money, plus calling for an update from someone who’s already updated us is annoying to the customer, so the cost of failure, while small, adds up.
Anyway, I just wrote a simple handler for that case to say “don’t bother calling.” Easy enough, but it would have been a hassle of a bug to fix, and we would have had the bug in the field for a while, then would have had to stop the calls while I fixed it.