The default structure for Xcode projects has a directory to hold implementation files and another directory to hold test files. There is no standard for where files are placed in each of the directories, but often we try to create a mirror version of the source files in the tests directory. We propose that co-locating […]
The default structure for Xcode projects has a directory to hold implementation files and another directory to hold test files. There is no standard for where files are placed in each of the directories, but often we try to create a mirror version of the source files in the tests directory. We propose that co-locating the test files with the implementation files provides a more seamless approach to development in an environment where a lot of emphasis has been put on testing.
By default, an Xcode project with unit tests has two targets, one for implementation files and one for test files, and two corresponding directories to hold the files. The two directories are purely for organizational purposes as any file in the project can be associated to any subset of targets in the project.
This style is also popular in “convention over configuration” environments, such as Rails and Maven. We find this approach to have many downsides.
We propose to co-locate the test file in the same directory as the implementation file, helping to solve some of these problems. This immediately doubles the number of files in a directory, but makes it obvious where to find the tests for a particular file. Even though the test file has been moved, we still maintain the target it is associated with.
No more hunting for where to find files or put files.
Navigating between implementation and test files is becomes much easier now. More interestingly, the test files are kind of analogous to header files, except they are also living code that describe how the API is expected to behave.
Browsing large amounts of code on GitHub is quite common these days, and co-locating the test files makes this much easier. People can easily jump between implementation and test, and are practically encouraged to do so now. It also aids in reviewing pull-requests since the implementation and test files will alternate instead of having them lumped together.
Tests should have some coding standards as implementation. Where we used to have a separate SwiftLint configuration for tests, we now use the same configuration for all code.
There’s not always a one-to-one correspondence between implementation and test. Some tests cover code in multiple files (e.g. integration tests or UI tests). Tests of that type should structured in the more traditional way, inside a separate directory. We could even go further and say that those types of tests should even have their own target and be separate from your unit tests.
We are now all onboard with this test co-location style. We have opened multiple pull-requests (by the way, we’re open source!) to bring each of our first-party dependencies (link, link, link) and the main app (link) into this world, and so far we’re loving it!
If any of this sounds interesting to you, we’re hiring!