31

A little over a year ago I was fortunate enough to be able to take a 9 month break from work. I decided that in that time time I would hone my C# skills. I started working on a bunch of projects and forced myself to follow TDD.

It was a fairly enlightening process.

It was tough at first, but over time I learned how to write more testable code (which, as it turns out, tends to be more SOLID code) and in the process I also sharpened my OO design skill.

Now I'm back in the workforce and I'm noticing something odd.

I prefer not to follow TDD.

I find TDD slows me down and actually makes it harder to design a clean application.

Instead, I've adopted a slightly (massively) different approach:

  1. Pick a vertical slice of work
  2. Develop a functioning prototype
  3. Refactor until everything is nice and tidy
  4. Sit back an appreciate the beautifully SOLID and testable code I've written.

You may have noticed that step 1 wasn't "define the public surface of my test target" and step 2 wasn't "test the bejesus out of said public surface." You may have also noticed that none of the steps involve testing. I'm writing testable code, but I'm not testing it... just yet.

Now, I'd like to make it clear that I'm not actually foregoing any kind of testing. The code I'm writing works. It works because I'm testing it manually.

I'd also like to make it clear that I'm not foregoing all automated testing either. This is where my process is different. And this is why I'm asking this question.

TDD in theory. Not in practice.

My process has evolved a bit and I've struck a balance between TDD and no tests that I find very productive and also reasonably safe. It goes as follows:

  1. Implement a working vertical slice of work with testing in mind, but don't write any tests.
  2. If down the road (eg, a month later) that slice needs modification
    1. Write Unit Tests, Integration Tests, Behaviour Tests, etc that guarantee the slice of work is correct
    2. Modify the code
  3. If that slice doesn't need modification,
    1. Do nothing

By simply shifting the burden of writing tests from before writing the code to before modifying the code I've been able to produce much more working code. And, when I do get around to writing tests I write far fewer of them but cover nearly as much ground (higher ROI).

I like this process, but I'm concerned it might not scale well. Its success hinges on developers being diligent about writing tests before they change things. And that seems like a pretty big risk. But, TDD has the very same risk.

So, am I going to [BT]DD hell, or is this a common form of pragmatic coding and testing?

I'd like to keep working this way. What can I do to make this process work in the long term?

Note:

I am the sole developer on my projects and I am responsible for everything: Requirements gathering, design, architecture, testing, deployment, etc. I suspect this is why my process is working.

18
  • 2
    Looks like spike and stabilize with out always doing the stabilizing if If that slice doesn't need modification. lizkeogh.com/2012/06/24/beyond-test-driven-development Jun 4, 2015 at 16:42
  • 13
    You're discovering something about TDD that I've long suspected, that the test first mantra is an exceptionally good learning tool, but it is not design, it merely encourages good design. In the end, what you want is testable code and unit tests that provide good code coverage and mirror the software's requirements; as you are finding out, you can get that without writing the tests first, if you practice sensible design principles. Jun 4, 2015 at 17:03
  • 5
    Yep, and writing tests first essentially doubles your prototyping work. Jun 4, 2015 at 17:04
  • 3
    it means I was lying about it being "slightly."
    – MetaFight
    Jun 5, 2015 at 21:29
  • 1
    "And, when I do get around to writing tests I write far fewer of them but cover nearly as much ground (higher ROI)" When you say write far fewer of them, do you just mean because you're only testing code you're changing, or are you saying that you're somehow covering the same (tested) piece of code with fewer tests than if you'd used TDD? Jun 10, 2015 at 11:09

9 Answers 9

7

To make the process work in the long term I would write the tests when the code is being written.

Which may seem to contradict your approach. However you've posed the question so I'll give you my take:

You don't have to write the tests before the code. forget that purity. However you want to write the tests around that time.
Once you have got the code working, you've tweaked it a bit, got some bugs out (we're talking about a timescale of hours here), you are then at a point of maximum knowledge about what the code is doing. This is a great time to write tests that capture your knowledge.

Leaving this until later means the knowledge will (naturally) diminish over time.

It also means that should you ever leave and should anyone else take over you will not have the immediate technical debt of not having documented (through tests) what does what.

Most of all, "some day" may not come. You may either get hit by a bus or you might board the bus for new adventures.

Finally, manual testing doesn't scale and frequently doesn't cover all the devices used by the end user.

2
  • I think I like your suggested approach and I'll probably apply it when I can. My work is very fragmented though, so "a timescale of hours" is not always possible. Unfortunately I'm on support too so I often get pulled off my work to help fight fires :) But, that's life.
    – MetaFight
    Jun 10, 2015 at 10:00
  • The issue though is that tomorrow never comes, there's always the next feature. And what are you going to chose to do; write the next feature, or write the tests for the thing you just "finished?"
    – Andy
    Jun 30, 2017 at 19:33
9

Although TDD is tough to implement 100% there is a flaw in your approach

  1. Implement a working vertical slice of work

    1.1 1 year passes....

    1.2 A new dev starts work on the project

  2. If that slice needs modification

    2.3 Parse 'Clean Coding' style method names and parameters 'GetUnicorn(colourOfUnicorn)'

    2.4 Read xml comments 'Gets A gold unicorn (for riding)(obvs)'

    2.5 Hunt down the original dev

    2.6 Hope they remember what the code is supposed to do

    2.7 Get them to explain it all

  3. Write Unit Tests, Integration Tests, Behaviour Tests, etc that hopefully guarantee the slice of work is correct

  4. Modify the code

I think you are correct to identify that unit tests really show their value when modifications are required.

9
  • 2
    Hey, I write self documenting code! My classes have one responsibility and are, therefore, easy to understand. Nobody will need to hunt me down :)
    – MetaFight
    Jun 4, 2015 at 17:06
  • 7
    @MetaFight and if they do, you'll be easy to spot on top of the solid gold living unicorn!
    – jonrsharpe
    Jun 4, 2015 at 18:24
  • 3
    I call him Goldicorn.
    – MetaFight
    Jun 4, 2015 at 18:25
  • On a more serious note, yes, you have a point. I've considered logging "testing debt" stories so that I can pay some back when my work load is lighter.
    – MetaFight
    Jun 4, 2015 at 18:47
  • 4
    If the code is written well, but has a bug, it will probably be fairly easy to understand what the original developer meant to accomplish by reading the code, and the new dev can add the necessary tests. The only problem is that most developers think they are writing good code. "Good" depends heavily on your perspective and experience as a programmer. So, there's a tension in this that has to be managed.
    – Phil
    Jun 10, 2015 at 12:22
4

I agree with both Daniel Hollinrake and Ewan, that the first key point why your test-only-if-modify works well so far is:

I am the sole developer on my projects and I am responsible for everything

and that a likely second key point is:

you're producing nice clean code

I do not think TDD brings a huge productivity boost for sole programmers, and it may not hugely improve the quality of your code if you are already writing good clean code.

However, TDD will surely improve the quality of the code of poor / inexperienced / obsolete programmers, especially when the time comes to modify the code without breaking anything else. And even more so if the person modifying the code is not the same person that wrote the code originally or several months have passed in between.

In other words, I think TDD is both good practice to improve the quality of your code (as you acknowledge yourself) but also (and more important) a sort of hedge when you are working with average or mediocre programmers (say, from a different department or a different company) which is a far more common situation than working solo.

1
  • 1
    I think part of the problem is that there may be only 1 programmer but the code base will often grow over time and what worked (for testing) when it was small doesn't keep working as it gets larger. Jun 10, 2015 at 13:44
3

For me the key thing appears to be this:

I am the sole developer on my projects and I am responsible for everything: Requirements gathering, design, architecture, testing, deployment, etc. I suspect this is why my process is working.

This works for you and you're producing nice clean code (I assume!). The only thing I would say you need to do is create a test harness so as other developers can come in and be confident in making changes. Also the test harness ensures consistency in the code's behaviour.

I think your approach is is similar to mine. I am usually the sole developer on my projects. I've found that an appreciation of TDD has enabled me to write smaller functions and cleaner code but I add tests whilst writing the code as a test harness. That way as the code evolves and functionality changes I can be reasonably confident in making changes.

A secondary reason for writing tests is that I feel they are a form of documentation. They can explain my reasonings behind why a function was created. But here, I'm thinking more about Behaviour Driven Development.

9
  • I'd say test suite would come in at 4th place for passing on to other devs - requirements doc, architecture diagrams and design docs would be much more important to communicate matters than a bunch of unit tests.
    – gbjbaanb
    Jun 10, 2015 at 9:40
  • It's a fair point, but sadly in my experience, on nearly every project I've worked on the documentation, when it exists, is either out of date or incomplete. Jun 10, 2015 at 10:25
  • 1
    Same here, which is why devs should realise the importance of documenting things and not writing more code in the form of tests! Maybe we need tools to allow for better documentation generation (ie that isn't just the pretty formatting of method signatures) from code comments and requirement tickets.
    – gbjbaanb
    Jun 10, 2015 at 10:45
  • I've edited my answer a bit in response to your comments. Thank you. Jun 10, 2015 at 11:05
  • 2
    @gbjbaanb If I can help it, I like to avoid writing requirements docs, architecture diagrams, and design docs. This is because they tend to go stale very fast. In my case I'm pretty lucky as I manage many small applications with very few responsibilities. This makes requirements and architecture documentation a bit overkill. And the projects are small enough that the overall design is clear. What I am documenting, however, is how the systems interact, how to deploy them, and how to monitor their health.
    – MetaFight
    Jul 14, 2015 at 12:02
3

Unit Testing is about tackling the problem of maintaining code. While there are people who say that they are faster writing code with TDD rather than without, I'm not surprised that you are able to write more new code without writing tests.

The issues that I can see with the practice of writing tests just before you change it:

I often need to make changes in a hurry

While you might save time overall by only writing tests when you need them, not all time is equal. Spending 2 hours writing tests to save 1 hour when I'm in crisis mode - totally worth it.

It easier to write tests at the same time as I write the code

To properly write unit tests you need to understand the code I'm testing. I often use unit testing as an exercise in understanding, but unit testing existing code can be time consuming because understanding existing code is time consuming. Contrast that to writing tests as you write the code and you will find it much quicker because you already understand the code - you just wrote it!


Michael Feathers definition of legacy code is code without tests. Regardless of whether you agree with his definition its clear that a substantial portion of the cost of modifying existing code is making sure that it still works as expected, often its not even clear what the expected behaviour is.

Writing unit tests offsets that cost by encoding an understanding of what the correct behaviour is, as well as providing an easy way for "future us" to check that behaviour is still correct.

2

This is a good question, and FWIW I'll throw in my two cents.

About a year ago I was coding in Salesforce, a platform which had an ingrained mechanism which forced you to not necessarily write tests before you coded, but rather forced you to write tests in general.

The way it worked was that the system would force you to write tests, and it would make a calculation of the number of lines of your code that were tested into a percentage. If all code throughout your production instance fell below 75% tested .. Salesforce no more work.

The end result of this was that every time you did anything in Salesforce you had to write or update tests. While I'm sure this has a huge impact on the market share of Salesforce, in terms of the life of a developer it was a massive pain in the ass.

A lot of the time you were just trying to get through a small ticket, and then testing comes in and doubles your development time, for a feature that you just know works.

Then the awkward concept of TDD swept through our department, right down to our databases. Our architects wanted to push out thorough testing into every aspect of our IT department. Slight pain in the ass, meet even greater pain in the ass.

Back then TDD never really made sense to me, and even now it still doesn't. A lot of the functionality I've written in my current role takes place in a similar mechanism to that which you've mentioned: in vertical slices which I refine until they work. When I was in that old role, and still now I often don't know what my code is going to do until I actually write it, so the idea that I can write tests to drive the code I'm going to write just.. doesn't make sense to me, is cumbersome, and mostly a waste of time.

All of that said, tests are wonderful and magical things which make everything right in the world. They make your code correct, they ensure your app does what you think it does, and generally everything smoother. The question then isn't whether you write your tests before you code, or after you code, the question is how much time are you going to commit to testing. That's the real problem, at least in my software development experience. Testing takes time and money and you have to do it within the framework of competing interests.

And so, in general I agree with you: TDD in practice is a bit awkward and cumbersome. At that point you need to keep in mind what works best in your current situation. If you're writing critical code, just make sure it's tested in general. If you've got the time, give TDD a try and see if it adds anything to the process.

4
  • 2
    I feel like we're about one language generation away from getting TDD right. I think right now TDD is sort of "bolted on" to most languages with xUnit frameworks. At some point it's going to just be built into how coding is done -- not separate. Like you'd define a class, and immediately the stubs for all the testing would be generated, along with some subset of the tests themselves (the ones that could easily be determined by the classes/methods themselves).
    – Calphool
    Jun 6, 2015 at 2:23
  • 3
    @Calphool We have actually integrated some easily testable things into the language! We call it static typing. Rust takes it further with borrow checking to test for even more bugs. But most tests are specific to that exact class ("if I click the button, the widget turns red") - how would the compiler/IDE possibly know you were going to test that?
    – user253751
    Jun 6, 2015 at 7:56
  • 1
    @immibis: Perhaps by extending type checking further. Perhaps the concept of "the widget turns red" becomes a first class concept that can be deduced in some way from the code. I don't claim to have the answers, I just feel like TDD is still new enough that it hasn't been fully integrated into language evolution.
    – Calphool
    Jun 6, 2015 at 10:53
  • 1
    Salesforce specifically has the testing all wrong: they make it required to have tests in place, but make it ridiculously difficult to write quality tests. It sounds great in theory, but in practice it makes developers want to gouge their eyes out with spoons.
    – user22815
    Jun 8, 2015 at 10:09
1

I could not recommend your approach.

If I use your approach it would be for example like the following(the house is the application):

  1. I start building a house for my family as a bricklayer with some knowledge or as beginner.
  2. I know the requirements like child rooms, guest room and start building my "prototype" house.
  3. Than a couple of times later your "prototype" house is done.
  4. I start looking if the structure is stable enough manually. So I pick up a lot of weights and bring it into the different rooms in the first floor. To make sure when I sit in a room with my family the ceiling don't break. But it breaks and I start refactoring. First cleaning all the mass up. Than build it new and test it again manually until it is stable enough.
  5. Than I move in with my family. All is fine.
  6. A moth later my cousins and parents are coming to visit us. But before they can enter our house they need to pay an architect and civil engineer to make sure the ceiling don't break when we sit in one of the rooms in the first floor.
  7. The architect and civil engineer have a lot of work because they have nothing to start with. So they need to go into my house and look how I build it.
  8. And again it is not stable enough. So they have to refactor the ground of the first floor.
  9. But after that everything is fine and all can safely enter my house.

So your approach costs a lot of time and a lot of knowledge before I build the house with your approach. Or it take a bunch of time! Also it is not pretty Gentleman to let other write tests for your code when there requirements has changed.

So there is a better approach without programming a "prototype" and than start refactoring. Instead of programming a prototype" make a Design with UML of your Application as follows.

  1. Create a UseCase diagram. You can use draw.io to start with.
  2. Than create a EPK diagram based on your UseCases to determine the behavior. (BEHAVIOR of your Application) Faster to refactor than refactor a coded prototype. Especially when you are a beginner.
  3. Create a class Diagram. (STRUCTURE of your Application)
  4. Determine where you could get trouble in the Implementation of the behavior.
  5. Write for that a simple prototype with maybe 10 or 20 lines of code to determine how you can implement this behavior. Good for beginner. Or watch a tutorial, look into source code of other example applications out there. How they solved it.
  6. Than start coding. Fist the succeed tests of your UseCase. This can be done in different ways. First create all the Structure that is needed for the test and that UseCase. When use Enterprise Architekt the structure can be generated for you. Based on your Diagrams. Or create the structure while wiring the Test. So no compile errors appear. Mention here is that you ONLY need to test the BEHAVIOR of your application. The UseCases you have.
  7. Than Implement the behavior of your UseCase.
  8. After the succeed UseCases start write Tests for the exceptions. And it feels always good when you see the green colors when your tests are valid ;)
  9. And you are done.

Sure this approach needs also some Knowledge in UML but it is fast to learn. And it is always faster to rename a Class or move arrows in a digram than do it in your IDE. But learning the use of test frameworks will be more exhausting at the beginning. Best is here to look run test of open source projects and look how they work. But when you ave a test driven application the next application will be much faster. And I think it's a good feeling to know everything works fine.

So I just down vote the approaches because they are very time consuming for beginners and not good after all. To have a clean borders between your structure and the behavior you can use the Domain driven Design and under Arrange very Domain with two Packages(one package named structure and the other named behavior). Also for your tests. simple example check out this Example written in java.

1

The code I'm writing works. It works because I'm testing it manually.

Did you test manually every possible branch of your conditions after little change? How long takes on feedback loop of your manual testing. How close it to feedback loop you get with automated tests.

Automated tests(doesn't matter test-first or not) makes you go fast - by providing faster feedback loop on your code.

Are you sure you will remember to test some condition manually after six month - please don't say you will document all important condition to test - because writing kind of documentation/comment is equals to writing test(executable documentation)

  • Pick a vertical slice of work

  • Develop a functioning prototype

  • Refactor until everything is nice and tidy

And again: while refactoring did you manually test all logic which get affected by refactoring? How long time takes to test on refactoring change? If refactoring breaks some code how long time takes you find a reason for breaks?

  • Sit back an appreciate the beautifully SOLID and testable code I've written.

Beautiful and clean code you enjoyed is very subjective. Your code can be clean and reasonable for you. Best method to check if your code really readable, understandable and testable is tests and code reviews made by other developers.

You found your way very productive only because you are only developer working with the code, and, I think, because you only starting working in this project(How old this project you working on? 6 - 8 month?).
You still remember everything you wrote and you can recognize a reason for possible problems. I am pretty sure you will start writing tests from beginning after 2 - 3 years of your project - because you want to be sure that you doesn't forget anything.

0

If you never make mistakes you don't really need tests. Most developers do make mistakes, but if you never do, and you are confident you will never make mistakes in the future (and you are the only one on the project), there is really no reason to waste time writing tests.

But your solution is kind of halfway because you are proposing to write tests when changing code, but at the same time your method assumes that you'll never make mistakes when you decide which parts of the code to write tests for. This only works if you always understand perfectly which areas a change may affect. I think many common developers (not you of course!) have experienced to make a change, and then a test somewhere unexpected fails, because you made a mistake.

Of course good architecture, SOLID principles etc. should prevent this happening, but most developers are not perfect, and this is why tests across the system are valuable.

1
  • Of course good architecture, SOLID principles etc. should prevent this happening - nope. Complex systems have parts that affect other parts, that's just how it is. e.g. modifying the Antlr grammar in Rubberduck can easily make the intended modified part work perfectly, while breaking 45 other features. Without thorough tests, there's no way to know, and you'd have to be insane to want to manually test all cases every time. If I change something in the resolver and 987 tests break, I know I did something wrong and what it affected. Jul 2, 2017 at 0:05

Not the answer you're looking for? Browse other questions tagged or ask your own question.