One of the things that bothers me when developing Rails apps is where tests are placed by default. Rails puts model tests in test/unit, controller tests in test/functional, and acceptance tests in test/integration. But the names of those locations doesn’t always match what those tests do.
The three main types of tests we typically write in Rails apps are Unit, Integration, and Acceptance. Unit tests work on an isolated units of code, exercising a single method or object. Integration tests work on a subsystem within an app, exercising the dependencies within the subsystem. Acceptance tests (previously known as functional tests) work on the application as a whole to determine if it performs according to the customer’s acceptance criteria. I encourage you to follow those links if you want to read more about the definitions and differences between these three types of tests.
Rails puts model tests under test/unit, but are they always unit tests? It is common to perform unit and integration tests on models, depending on what the model is responsible for and how it is designed. In my experience, many Rails apps have a problem overloading the User object with too many responsibilities and dependencies. And the tests for those dependencies are by definition integration tests. However, the location of those tests tell us that they are unit tests.
Off the top of my head, here are some distinctions:
- How many code paths are executed? A unit test will typically execute very few code paths, functional and integration testing many.
- If a test fails, how many things could be broken? For a unit test, the ideal answer is 1. Functional and integration tests the answer could be large.
- How long do they take to execute? Unit tests execute quickly, functional and integration tests relatively slowly (in general).
- Whose perspective is used to read the test? Unit tests are read by programmers. Functional tests are read by domain experts. Integration tests are read by architects (those with a broad understanding of the design of the system).
That said, here some distinctions I don’t make:
- Who writes them? I write tests at all these levels. Where I work (Facebook), programmer responsibility is very broad, so mastering all styles of testing is essential.
- When are they written? I move fluidly between these different styles of tests–a functional test to make sure that I understand the domain, a few unit tests to capture subtle variations, an integration test to make sure I’m calling an external service correctly, a few more unit tests, …
- When are they run? Ideally, they are all run at the same time, and are fast and stable enough to give nearly-instant feedback.