Reasonable defaults can make your life easier. I think that most programmers agree with that. Does that mean we should stick to them forever? Can a particular solution be suitable for everything and everyone?
This article tells the story of my experiment, which proved that sometimes it is worth breaking well-established standards.
To get some context
Let's start with the standard elixir project.
. ├── config │ └── config.exs ├── lib │ ├── foo │ │ ├── bar.ex │ │ └── baz.ex │ └── foo.ex ├── mix.exs └── test ├── foo │ ├── bar_test.exs │ └── baz_test.exs ├── foo_test.exs └── test_helper.exs
It's the output of
mix new foo with two additional modules:
Foo.Baz added manually. You can imagine this is your favorite small open-source library.
In my opinion, it has the following advantages:
- compiles the content of
libout of box
- has everything that is needed to run tests
- looks familiar to many developers
- is easy to be released to hex (the content of
libis added to the package, the content of
That's great. So... what's your problem?
I don't like having separate
test folders in phoenix projects. Ones that are big and not meant to be pushed to hex.
I'm bad at remembering shortcuts and names. On the other hand, I'm pretty good at remembering where individual files are stored (especially when the project has a clear division into small contexts). However, I don't want to go through the second tree with the same structure to get to the tests. It has always been a bit annoying.
One day while reading the documentation of
mix test task, I found out the following options:
:test_pattern. The mere fact that such options exist in Elixir codebase means that they were useful to someone. I decided to check if they would also be useful for me.
First, I changed
:test_paths value to
["lib"]. It allowed me to keep a file with tests next to the file with implementation. It also forced me to move
.test_helper.exs to the root of
lib, but one additional file isn't a problem. What bothered me more was the alphabetical order. Of two files with the same prefix, the one ending with
_test.exs was higher than its
.ex counterpart. It made the files tree in my editor look weird. Luckily, it was easy to fix by changing
:test_pattern value to
"*.test.exs". From now on, files with tests had to be named like this:
. ├── config │ └── config.exs ├── lib │ ├── foo │ │ ├── bar.ex │ │ ├── bar.test.exs │ │ ├── baz.ex │ │ └── baz.test.exs │ ├── foo.ex │ ├── foo.test.exs │ └── test_helper.exs └── mix.exs
I was pleased with what I was able to achieve.
Ok. Is that all?
After a few weeks of work in this way, new advantages appeared:
- Code review has become easier because changes to tests are right under changes to implementation (long-gone days of repeatedly scrolling the whole merge request on GitLab up and down).
- Faster refactoring (move single folder when changing namespaces)
- Well-written tests are a form of documentation. It is good to have them in an easily accessible place.
- It's visible if a module is important or not. The file generated by a framework or library doesn't have an additional
.test.exsfile associated with it.
It also has some disadvantages:
- I have to set manually
:test_patternfor every new umbrella application.
- It may not be obvious where to put integration tests when there is no
However, these are minor ones comparing to how more comfortable my work has become.
I'm aware this topic may be controversial for some people. I encourage you to leave me a comment if you think that I've missed something.