Question: Another name for this question could be: How to TDD the main function?

My situation: I'm writing a program from scratch that runs a sort of simulation, and I want to apply TDD to the whole development process. It seems straightforward to me when I think about the small detailed features, but at some point the program needs to *start*. For example, this is a rough outline of how the program will run:

1. Read data from a database.
2. Loop a bunch of times simulating the desired behavior.
3. Write the output somewhere.

I'm tempted to write a test like `testLoopProducesValidOutput` but that's basically just checking to see if the entire program ran correctly, which doesn't seem very unit-like for a unit test.

In the spirit of "don't write code unless you have a failing test", I don't quite understand what kind of test to write to actually get that initial high-level code written that isn't running the entire thing. How would one generally approach this kind of situation?

(Of course one solution is just to not strictly follow TDD for this part, but if there's another solution I'm open to learning before I give up. :) )

Answer: 
TDD isn't limited to unit tests, it can be applied at any granularity. What you're trying to do is covered by [acceptance test-driven development](https://en.wikipedia.org/wiki/Acceptance_test-driven_development) (ATDD) which adds an outer loop to the classic red-green-refactor cycle: you write a failing acceptance test first which should help you come up with a skeleton of your overall program, then you write failing unit tests before finally implementing the actual logic. The acceptance tests ensure that you are building the right thing and your various components are wired together correctly, while the unit tests ensure that your components are implemented correctly.

There is an excellent book on this topic called [Growing Object-Oriented Software, Guided by Tests](https://rads.stackoverflow.com/amzn/click/com/0321503627) that goes into detail about this process and includes a worked example of a GUI application built from scratch using TDD.
I think the point is being missed here.

You have two questions in fact:

* One is how to test a function/class.
* The other is what a test means in TDD

---

Testing a function/class should already be quite well practiced by you based on the question.

The answer is dependency injection.

The problem is that your main function assumes to know more than it should. It does not know every situation in which it might be created, same as any other class/function.

The solution is to humble it. It should accept those dependencies somehow, be it through a constructor, a property (which has some default), or an optional parameter.

This will make the program even more capable, as it is now much more configurable and testable.

If for some reason the test is complex to setup, then the function/class needs to be broken down. After all you are essentially asking anyone who uses your function/class to know all of this information in order to use it well. Most of the time that user is yourself.

---

As for the second question (which you didn't state but is lurking back there)...

English is tricky in that the meaning of a word can be broad or even change in a different context or usage. 'Test' is one such word.

The problem is that there are several kinds of test:

* Aspirational Tests: These test describe something to be desired. In short a kind of Specification.
* Adversarial Tests: These tests probe for weaknesses and exploitable gaps. In short evidence of safety/security.
* Affirmational Tests: These tests pin down something that exists. In short evidence of organic code.

In TDD the goal is to write Aspirational tests.

The best time to write an aspirational test is when the code doesn't exist, or in a pinch without looking at the code (and you've had it unload from your own memory).

The worst time to write such tests is when the code exists. The temptation is very high to instead write an Affirmational, or an Adversarial test. Those tests have their benefits, but they won't deliver the thinking that an Aspirational test does.

An Aspirational test challenges you to simplify your tests. The simpler the tests:

* the fewer prerequistes/knowledge needed to use the code effectively,
* the more likely the code can be reused,
* the easier it is to change the aspired for behaviour.
* the easier it is to burn the implementation down and rebuild it,
* the easier it is for another programmer (or your future self) to understand and maintain the code

Similarly this principal can be applied at higher levels of modularity under different names. eg: BDD, ATDD.