In Rails apps that use RSpec, it’s customary to have a
spec directory with certain subdirectories named for the types of tests they contain:
system. The Minitest organization scheme doesn’t share the exact same nomes but it does share the custom of organizing by test type.
I would like to raise the question: Why do we do it this way?
To get at the answer to that question I’d like to ask a broader question: What’s the benefit of organizing test files at all? Why not just throw all the tests in a single directory? For me there are two reasons.
Reasons to organize test files into directories
Sometimes I’m changing a feature and I want to know where the tests are for that feature so I can change or extend the tests accordingly
When I’m making a change to a feature, I usually want to know where the tests are that are related to that feature so I can update or extend the tests accordingly. Or, if that feature doesn’t have tests, I want to know so, and with a reasonable degree of certainty, so that I don’t accidentally create new tests that duplicate existing ones.
Running tests in groups
If tests are organized into directories then they can be conveniently run in groups.
It is of course possible, at least in some frameworks, to apply certain tags to tests and then run the tagged tests as a group. But doing so depends on developers remembering to add tags. This seems to me like a fragile link in the chain.
I find directories to be better than tags for this purpose since it’s of course impossible to forget to put a file in a directory.
Test type vs. meaning
At some point I realized that if I organize my test files based on meaning rather than test type, it makes it much easier to both a) find the tests when I want to find them and b) run the tests in groups that serve my purposes. Here’s why.
When I want to find the tests that correspond to a certain feature, I don’t necessarily know a lot about the characteristics of those tests. There might be a test that matches the filename of the application code file that I’m working on, but also there might not be. I’m also not always sure whether the application code I’m working on is covered by a model test, a system test, some other type of test, some combination of test types, or no test at all. The best I can do is either guess, search manually, or grep for some keywords and hope that the results aren’t too numerous to be able to examine one-by-one.
If on the other hand the files are organized in a directory tree that corresponds to the tests’ meaning in the domain model, then finding the tests is easier. If I’m working in the application’s billing area, for example, I can look in
spec/billing folder to see if the relevant tests are there. If I use a nested structure, I can look in
spec/billing/payments to find tests that are specifically related to payments.
I don’t need to worry about whether the payments-related tests are model tests, system tests or some other type of tests. I can just look in
spec/billing/payments and work with whatever’s there. (I do, however, like to keep folders at the leaf level with names like
system, etc. because it can be disorienting to not know what types of tests you’re looking at, and also it can create naming conflicts if you don’t separate files by type.)
Running tests in groups
I don’t often find it particularly useful to, say, run all my model tests or all my system tests. I do however find it useful to run all the tests in a certain conceptual area.
When I make a change in a certain area and I want to check for regressions, I of course want to check in the most likely places first. It’s usually more likely that I’ve introduced a regression to a conceptually related area than a conceptually unrelated area.
To continue the example from above, if I make a change to the payments area, then I can run all the tests in
spec/billing/payments to conveniently check for regressions. If those tests all pass then I can zoom out one level and run all the tests in
spec/billing. This gives me four “levels” of progressively broader regression testing: 1) a single file in
spec/billing/payments, 2) all the tests in
spec/billing/payments, 3) all the tests in
spec/billing, and 4) all the tests in the whole test suite. If I organize my tests by type, I don’t have that ability.
On breaking convention
I’m not often a big fan of diverging from framework conventions. Breaking conventions often results in a loss of convenience which isn’t made up for by whatever is gained by breaking convention.
But don’t mistake this break from convention with other types of breaks from conventions you might have seen. Test directory structure is a very weak convention and it’s not even a Rails convention, it’s a convention of RSpec or Minitest. And in fact, it’s not even a technical convention, it’s a cultural convention. Unless I’m mistaken, there’s not actually any functionality tied to the test directory structure in RSpec or Minitest, and so diverging from the cultural standard doesn’t translate to a loss of functionality. It’s virtually all upside.
- The benefits of organizing tests into directories include to be able to find tests and to be able to run tests in groups.
- Organizing tests by meaning rather than type makes it easier to find tests and to run them in groups in a way that’s more logical for the purpose of finding regressions.