YOU DONT UNIT TEST? DevHour #1 Theo

Found this on my Youtube feed today after watching another clip on Merge vs Rebase workflows (which was really cool to watch BTW)… The (in)famous Primeagen talks with this Theo guy (ex Twitch apparently) about their opinions on Unit Tests, very fun to watch…

So, I’m sharing this because I think I can collect some diverse opinions on the topic; especially how you think Elixir plays a part (if any) in helping solve the problems they talk about. Even though I have my own opinions on the topic, I can’t personally relate to some of the struggles because I haven’t worked on this kind of scale (like Netflix and Twitch). Therefore, it would be awesome to hear from y’all, critics, adepts of TDD, and everyone in between.

Disclaimer: The video contains a lot of strong opinions, sarcasm, and even a little bit of humorous trash-talking about Elixir near the end, so have fun and take all of it with a huge grain of salt.

Enjoyed this, love the passion.

My own take on TDD is that it’s a personal style. If you’re writing a feature, no one can “prove” that you followed TDD, you can write the whole feature and then retroactively write the tests, obviously. But on teams, precisely when we’re doing code review, I find it really helpful when there are unit tests to document how the logic is supposed to behave with various inputs. So I wonder why TDD is being linked so closely with the value of unit tests generally.

My other main thought is that FE development is fundamentally a different beast than BE. Writing elixir apps I find it almost trivial to add really good, but basic, unit tests (also 100% agree that “coverage” is at best a misleading metric). Mocking external factors in elixir code is so much easier than mocking UI state in a react app. I still find unit testing really valuable on the FE but it definitely extracts a steep price in terms of dev time, and that should be considered.

2 Likes

I am just starting to watch but at the beginning of the video they both agree that TTD sucks because no one wants the tests to direct the architecture design. I strongly disagree because one does not just start by writing tests. There is a thinking phase before, were architecture decisions are made. And then you can let the tests drive the implementation of the different modules interfaces/API.

I think in Elixir there are two styles of modules : big modules and small modules. The big module kind is where you have one module that manipulates multiple types (records, structs, tuples, and lists of such types), and where structs module contain only the defstruct, and maybe a new function and a couple helpers. The small module kind is where each module, notably struct ones, contains all the logic related to the type. I find myself fighting for that pattern, because it is heavily influenced by OOP, while I find that big modules are better to write complicated logic involving multiple types, all in one place (though the struct or data-structures modules are still useful to handle everything opaque – like how a username is stored in memory for instance, but not how we use that username).

And I feel TDD is perfect to find the good design of such functional code.

Now when I think architecture in Elixir, I think processes. Pure functional code is easy to write, and easy to change, it is about algorithms. TDD does not focus on algorithms, rather on APIs and interfaces. Complex algorithms are in defp land and TDD does not force you about how to write that. It helps you make your code more testable, more modular.

For process architecture, the thinking phase is crucial, the sup tree design is very important, and you should come up with a full design that works before starting to write code. TDD is not very helpful here, but neither does it enforce any architecture. And that TDD is not helpful there is only partially true. If you use TDD to design a modular functional core that has usable APIs, it will be easier to use in higher layers (gen servers, tasks, subscribers), and that higher layer will be easier to design once the core is ready, because you will have choices.

Two remarks though:

First, I often find myself writing small prototypes without any tests in .exs files, just to verify that some design could work. Thas is code to throw away when the concept is proven to work, and the TDD phase can start. It is not forbidden to write code, throwaway code, during the “thinking” phase.

Second, I know “architecture” is not only processes. We could call that “system” architecture, but there is also “architecture” in the choice of how some complicated data pipeline is designed. But that generally boils down to the same old patterns we always use : repositories for persistence, a transform layer that converts data structures to other data structures, queues to manage the load, etc. Sprinkle a couple adapters on top of that when you have to deal with different shapes for the same “thing” and you are good to go. TDD is perfectly compatible with that. On the opposite, I find mylself bringing a lot of useless abstractions when I think too much about that kind of architecture without writing code and tests.

Edit: regarding their criticism of Elixir, it is “I would never recommend anybody ship it but we got a lot done with it”.

Personally, I like to start with a big integration test.

Just use comments to map out the most complicated process front to back, the replace the comments with tests the whole way down (with no UI). I find that this approach plays really nicely with forcing you through a lot of architecture decisions.

Then, when that primary use case is working I’ll go fill in more targeted tests around different types of data in different pieces of the system.

And after all that, I’ll have a working and stable backend that’s ready for a UI.

2 Likes