IfI have a fairly complex object with a complex method, and I write my test and the bare minimum to make it pass (after it first fails, Red). When do I go back and write the real code? And how much real code do I write before I retest? I'm guessing that last one is more intuition.
Edit: Thanks to all who answered. All your answers helped me immensely. There seems to be different ideas on what I was asking or confused about, and maybe there is, but what I was asking was, say I have an application for building a school.
In my design, I have an architecture I want to start with, User Stories, so on and so forth. From here, I take those User Stories, and I create a test to test the User Story. The User says, We have people enroll for school and pay registration fees. So, I think of a way to make that fail. In doing so I design a test Class for class X (maybe Student), which will fail. I then create the class "Student." Maybe "School" I do not know.
I liken this to thinking about Recursion. Recursion is not a hard concept. It may be harder to actually keep track of it in your head, but in reality, the hardest part is knowing, when the recursion "breaks," when to stop (my opinion, of course.) So I have to think about what stops the Recursion first. It is only an imperfect analogy, and it assumes that each recursive iteration is a "pass." Again, just an opinion.
In implementation, The school is harder to see. Numerical and banking ledgers are "easy" in the sense you can use simple arithmetic. I can see a+b and return 0, etc. In the case of a system of people, I have to think harder on how to implement that. I have the concept of the fail, pass, refactor (mostly because of study and this question.)
What I do not know is based upon lack of experience, in my opinion. I do not know how to fail signing up a new student. I do not know how to fail someone typing in a last name and it being saved to a database. I know how to make a+1 for simple math, but with entities like a person, I don't know if I'm only testing to see if I get back a database unique ID or something else when someone enters a name in a database or both or neither.
If I have a fairly complex object with a complex method, and I writemy test and the bare minimum to make it pass (after it first fails,Red). When do I go back and write the real code? And how muchreal code do I write before I retest? I'm guessing that last one ismore intuition.
Cool. Now we've removed the duplication and created a well named function. What's the next test we can write that will force us to change the code? Well, we've been avoiding the case where the number is divisible by both 3 and 5. Let's write it now.
So, I was able to do this simply by looking for a test that I knew wouldn't pass at each step, but I've had a lot of practice. When I'm at work, things aren't ever this simple and I may not always know what test will force a change. Sometimes I'll write a test and be surprised to see it already passes! I highly recommend that you get in the habit of creating a "Test List" before you get started. This test list should contain all the "interesting" inputs you can think of. You might not use them all and you'll likely add cases as you go, but this list serves as a roadmap. My test list for FizzBuzz would look something like this.
So long as the tests that you write actually encompass your product requirements, once they are passing then the code is complete. Think about it, if all of your business requirements have a test and all of those tests are green, what more is there to write? (Okay, in real life we don't tend to have complete test coverage, but the theory is sound.)
I agree that lots of tutorials about TDD are simplistic. That works against them. A too-simple test for a method that, say, computes 3+8 really has no choice but to also compute 3+8 and compare the result. That makes it look like you'll just be duplicating code all over, and that testing is pointless, error-prone extra work.
When you're good at testing, that will inform how you structure your application, and how you write your code. If you have trouble coming up with sensible, helpful tests, you should probably re-think your design a bit. A well-designed system is easy to test -- meaning sensible tests are easy to think of, and to implement.
When you write your tests first, watch them fail, and then write the code that makes them pass, that's a discipline to ensure that all your code has corresponding tests. I don't slavishly follow that rule when I'm coding; often I write tests after the fact. But doing tests first helps to keep you honest. With some experience, you'll start to notice when you're coding yourself into a corner, even when you're not writing tests first.
But don't think that the real code appears like magic -that's wrong.You need a better understanding of what you want to achieve and then you need to pick the test accordingly, starting from the easiest cases and corner cases.
For example, if you need to write a lexer, you start with empty string, then with a bunch of whitespaces, then a number, then with a number surrounded by whitespaces, then a wrong number, etc. These small transformations will lead you to the right algorithm, but you don't jump from the easiest case to a highly complex case chosen dumbly to get the real code done.
When you're about to add a feature the refactor part is what you change before the the next test. You refactor the code to make room for the new feature. You do this when you know what that new feature will be. Not when you're just imagining it.
In the red phase you do anything to make the test pass as quick as possible and at any cost. You completely disregard what you've ever heard of good coding practices or design pattern an alike. Making the test green is all that matters.
In the refactoring phase you clean up the mess you just made. Now you first look if the change you just made is the kind of the top most in the Transformation Priority list and if there is any code duplication you can remove most likely by applying a design patter.
What this means is that even though You are changing the code, the conditions the code satisified, are left unchanged. And the checks (tests) You implemented to verify Your code are already there to verify if Your modifications changed anything. So the code You wrote the whole time is in there, just in a different way.
Another reason You might think that it's not real code, is that You're doing examples where the end program can already be forseen by You. This is very good, as it shows You have knowledge about the domain You are programming in.
But many times programmers are in a domain which is new, unknown to them. They don't know what the end result will be and TDD is a technique to write programms step by step, documenting our knowledge about how this system should work and verifing that our code does work that way.
When I read The Book(*) on TDD, for me the most important feature which stood out was the: TODO list. It showed to me that, TDD is also a technique to help developers focus on one thing at a time. So this is also an answer to Your question aboout How much Real code to write? I would say enough code to focus on 1 thing at a time.
The point behind red-green-refactor is that writing the correct tests first gives you the confidence to know that the code you wrote to pass the tests is correct, and allows you to refactor with the confidence that your tests will inform you as soon as something breaks, so you can immediately go back and fix it.
In my own experience (C#/.NET), pure test-first is a bit of an unattainable ideal, because you can't compile a call to a method which doesn't yet exist. So "test first" is really about coding up interfaces and stubbing implementations first, then writing tests against the stubs (which will initially fail) until the stubs are properly fleshed out. I'm not ever writing "failing code", just building out from stubs.
In code that I've written the integration tests a library with various test programs that read data and feed it to the library, then check the results. Then I do it with threads. Then I do it with threads and fork() in the middle. Then I run it and kill -9 after 2 seconds, then I start it and check its recovery mode. I fuzz it. I torture it in all kinds of ways.
And I just thought of this, but maybe you don't know when you are supposed to be done writing unit tests. You are done writing unit tests when your tests exercise everything that you specified it should do. Sometimes you can lose track of that among all of the error handling and edge cases, so you might want to make a nice test group of happy path tests that simply go straight through the specifications.
You are going to learn lots of coding 'theories' and 'techniques'. They're great for passing the time on overpriced student courses, but of very little benefit to you that you couldn't read in a book in half the time.
The job of a coder is solely to produce code. Code that works really well. That is why you, the coder plans the code in your mind, on paper, in a suitable application, etc., and you plan to work around possible flaws / holes in advance by thinking logically and laterally before coding.
But you need to know how to break your application to be able to design decent code. For example, if you didn't know about Little Bobby Table (xkcd 327), then you probably wouldn't be sanitising your inputs before working with the database, so you wouldn't be able to secure your data around that concept.
TDD is just a workflow designed to minimise the bugs in your code by creating the tests of what could go wrong before you code your application because coding can get exponentially difficult the more code you introduce and you forget bugs that you once thought of. Once you think you've finished your application you run the tests and boom, hopefully bugs are caught with your tests.
TDD is not - as some people believe - write a test, get it passing with minimal code, write another test, get that passing with minimal code, etc. Instead, it's a way of helping you code confidently. This ideal of continuous refactoring code to make it work with tests is idiotic, but it is a nice concept amongst students because it makes them feel great when they add a new feature and they're still learning how to code...
3a8082e126