Test code is sometimes more difficult to maintain then production code for various reasons, but chief among these reasons is that developers just don't know how to organize their test code. Poorly organized tests lead to poor performing tests, which leads to tests not being run as frequently as they should, which leads to more frequent and longer broken builds, which leads to fewer checkins, which leads to a loss of productivity, which leads to helpless little kittens dying in Africa*...
Test code should be organized along test types: unit, integration, functional, acceptance, and perhaps some others that tend to be more QA specific. I often use unit and integration as the base primitives for my code bases. Its not uncommon that I will have 2 test assemblies (unit and integration) for each assembly under test. Essentially all my fast unit tests that I constantly run are in the unit test project, while all the slower tests (that hit the DB) go into the integration project.
Why the fine grained separation? Why not just use one monolithic test project per solution?
The reason we split test code up is the same reason we split up a regular application, for maintainability. Well tested applications have an equal or greater number of lines of test code when compared to the actual application code. If we had just one test project for an entire solution it just wouldn't be manageable for anything but the smallest of solutions.
By splitting the unit and integration (aka slow) tests into their own project it becomes much easier to run just the fast in memory unit tests all the time. Categories are nice, and indeed useful for test segregation, however it still requires some filtering in the testrunner to only run the unit tests. Splitting unit and integration tests into separate projects just makes things faster and reinforces the intent of each particular test area.
As the code base grows we will find that build times increase. By having smaller test projects we may find we're not building as much code which helps us keep the TDD rhythm. It also gives us the flexibility to move our core library to a separate solution if we wish, which may be beneficial if there is a lot of dependencies on that library project. If we had one solution wide test project we wouldn't be able to do this.
Speed is King
Your unit tests need to be blazingly fast, anything else will lead to less effective tests. Tests need to be run often to be relevant. What you will find over time is that tests that hit external dependencies like databases or services are at least several times slower than a real unit test that has no external dependencies. Several hundred tests that hit a database will absolutely kill your test speed.
The important distinction between integration and unit tests is speed, test speed should guide you where to put a particular test. Slower tests go into the integration project, while the fast tests go into the unit test project.
The distinction between unit and integration tests isn't whether the SUT is completely isolated from its collaborators. Its OK to have unit tests that are using concrete collaborators, no where is there a rule requiring us to mock all collaborators. Does the system really need a proliferation of interfaces that are all internal to the assembly itself? I would argue in the general case no, you're probably wasting your time writing and maintaining too many interfaces. Interfaces should be used as general extensibility points not only as mocking extension points.
The reason we call slow tests integration tests is that they usually have one thing in common, access to an external resource (like a database). External calls outside the process are a magnitude slower than in process calls. When choosing what type of test to write, its almost always preferable to choose to write a unit test. Your unit tests are your first line of defense against defects.
When to run
With our tests organized and segregated between fast and slow, we can make further optimizations to our build pipeline.
Unit tests, specifically the fixture(s) that are relevant to what a developer is working on will get run over and over again while development takes place. Before checkin, ideally several times a day, a developer will run all the unit tests. This keeps the feedback on their progress immediate, which is only possible because we have fast unit tests.
Upon checkin our multiphase CI process takes the code, builds it, and runs all the unit tests. This ensures a minimal amount of functionality about the software that should be good enough to prevent any blocking issues for other developers. This build fails/passes the build and should always be kept green and fast.
The second phase of our build process then goes through and runs all of the slow integration tests as a secondary build. Depending on the amount of hardware and number of tests this may happen on every build or at scheduled intervals; perhaps a nightly build process runs all the slow build targets, things like: integration tests, creating the installer, and API documentation.
Failing integration tests should ideally be rare, except for perhaps legacy systems that lack unit test coverage. Broken integration tests almost always means that you are missing unit test coverage somewhere. If possible your first step to fix a broken integration test is to write one or more failing unit tests and then make them green. Once you have those unit tests in place, go back and run the integration tests before checking in.
Summary
With some organization and thought about where to place and how to write your tests you will be in a much better position a year down the road when your product's code base has really grown. By keeping the unit tests fast we keep the build fast, which enables faster turnaround on checkins and features. Do it for the kittens!
*I don't have any numbers to back up my claim about poor innocent kittens being harmed by poor performing unit tests, but it makes me take pause. Can I save a kitten with this test?