A recurring respondents’ theme is “TDD is irrelevant in front-end code”.
It’s easy to offer/receive this comment combatively, but I think a little more rich discussion of the factors involved might bring us to new and different positions about UI and TDD.
Most folks who offer that are living in some sort of JS world: their code is client-side scripts attached to html pages to render various contents received from another application. Their browsers are in effect frameworks, inside of which all their code runs.
(Aside: One of the most prevalent trade problems caused by the explosive demand for software is the inexperience of your average geek on the street. It’s easy to fall in to the “infinite now” and the “infinite here”: assumptions that what one does now is what all do always.)
I want to step back from that JS world for a minute. Instead, I want to consider a bog-standard old-school single-computer single-program application, one that one user uses on one computer to perform multiple tasks, some of which are reasonably but not insanely complex.
I own roughly 40,000 music files, in a variety of digital formats, all in a rather sophisticated arrangement I invented called “nascent dumpster fire”, spread out over hundreds of oddly nested folders with inconsistent names on multiple drives.
Imagine an app that could help me organize that so that it resembles how I actually think of it.
(THIS IS NOT AN INVITATION FOR YOU TO TELL ME HOW YOU MANAGE YOUR COLLECTION, EITHER BY CONCEPT OR BY TOOL.)
There’d be literally dozens of possible tasks, and there’s the concept of virtual layers and of WIP and of what-if. It’d be a serious piece of work.
Anyway, if we imagine that app, we can see it as having pixels at the top, bits at the bottom, and all kinda random crap in the middle. In fact, let’s push on that idea, of having rough horizontal layers.
At the very bottom there is metal. Above it sits an O/S. Above that sits the language. Above that is the standard libraries. Above that is the non-standard-but-standard libraries. Call all of that “the base”.
I lump all of that together from the TDD perspective for straightforward reasons: 1) It generally “just works”. 2) It generally is expensive to roll tests against. 3) It generally isn’t fixable by me if it’s broken. It all forms a kind of single-texture background.
Please note the use of the word “generally” above. I’m aware it doesn’t always work, it can be cheap to test, and it can be fixable by me. It’s just that it isn’t generally any of those three.
Above that there’s a handful of “our primitive generic extensions”. This is actually the leading edge of the problem domain. Standard libraries have classes with API’s that are frequently either “too thin” or “too thick”. This thin layer corrects that kind of infelicity.
This kind of thing is mostly the result of bitter old people like me and their nightmarish prior failures. Two examples from Java: how we handle money and how we handle references to physical files. (Nobody implements a money-centric app using raw BigDecimals *twice*.)
For me, this stuff is super TDD’d. It’s because a) we wrote it, and b) it has branching logic, and c) fool me once, shame on you, fool me 371 consecutive times, shame on me.
Then we get to the “domain proper”. In our app, that involves things that represent virtual and physical files & folders, file formats, links between them, tagging both internal to files and external to them, playlists, step execution queues, and so on.
This full-on “domain code” is also TDD’d pretty rigorously. It represents in a very real sense what folks normally call the “business logic” of GeePaw’s Imaginary Music App.
Now jump with me, to the very top. There, we have not a library but a framework. Like the layer we called base, this isn’t our code. Unlike the base, though, code that is our code is called by it, a lot. That’s the distinction I mean by framework vs library: who calls who?
However, the UI framework is otherwise just like the base. 1) It generally works, 2) It’s generally expensive to test. 3) It’s generally not fixable by me when broken.
So guess what: like the base, TDD’ing it doesn’t give much value.
But, we’re not done yet.How is that UI framework connected to all that code at and below the domain? At its simplest, it’s connected by a bunch of data declarations. Modern UI’s are based almost entirely on fairly sophisticated usage of two GoF patterns: Observer and Command.
The framework *watches* things, and in so doing it transforms itself, and it *emits* things, and in so doing it transforms the domain. And when we write UI what we’re doing is telling the framework what to watch and what to emit in response to a user action.
Take the two simplest possible cases: the editable name of a domain class that represents one of those virtual/physical music files. You tell an editor “watch this domain thing’s field.” and “say ChangeName(newName) when the user edits it”.
This can normally be accomplished in either one or two lines of data declaration. Accomplishing it that easily is the main reason UI frameworks even exist.
Do I test these declarations using automated TDD tests?Oh, hell no. Look, I work for a living, I don’t have time to write tests to achieve intellectual purity or satisfy a coverage metric. The only reason I write tests at all is because people pay me to add value, and in some cases writing tests lets me add value far more quickly.
A single GAK (Geek At Keyboard) run will tell me whether the thing shows the value and whether it sends the command. And if it does it once, it’s not gonna not do it the second time.
The same rule applies to most layout situations. 1) It’s a data declaration. 2) It’s a one-liner. 3) When it works once in GAK it pretty much works forever. That plus the “base”-like aspect of UI frameworks adds up to nope, nope, nope, no automated TDD tests.
There’s just one niggling question here: is it always the case that using a UI framework is just a collection of one-liners where we hook up a simple observer and issue simple command objects to the domain?
The answer to that question is “No, decidedly not”. Modern UI’s aren’t, normally, just collections of simple dialog boxes containing editor controls watching an observable string and emitting confirmed “change this value” commands.
They are full of lots of things that aren’t editors, and they’re richly cross-connected. They watch mixed collections, they manage master-detail, they maintain ideas of what is and isn’t permissable, and it’s often nested and nested and nested again.
Bottom line, all that crap is code. Real code. And we write it. With branching logic. With intricate scenarios. With complex conditionals.So where’s that code live? There are exactly three choices: 1) in the UI framework, 2) in the domain classes, and 3) some other damned place we haven’t invented yet.
Because it’s real code, we want to test it. We reject putting it in the UI frameworks because they’re like “base”: testing code inside them is slow and unpleasant.
But we also reject putting it in the domain code, because list ordering or selection status or cross-field choice restrictions just don’t feel like they belong there.
So the answer — I’m sure many of you know this already — is some other damned place. The standard name for that used to be Model, but so many bad teachers and bad examples made Model a synonym for Domain, a lot of modern sources call that place the ViewModel.
Remember we worked our way up from the metal to the domain, but then we *jumped* to the top? We were skipping the layer where ViewModels live.
A ViewModel is a thing that is specific to a particular view. It lets us use simple data declarations going up to the UI, but complex code going both across its own fields & function as well as down to the domain.
And the thing is? Not only does it need testing, but it’s far *easier* to test than if we left that code up in the UI framework.
Do I TDD ViewModels? I most definitely do, and fairly rigorously. It’s straightforward, by and large, doesn’t require me to set up a running app or even the UI framework. The tests look like regular ol’ microtested TDD.
And now, let’s spiral all the way back out, away from the single-computer single-user single-language app and back to the world of “front-end” work in JS.The one question we have to answer, to decide whether TDD is useful in that world, isn’t a generic question, it’s one whose answer depends almost entirely on the asker’s context.
“Is the ViewModel stuff on my side of the http transport or on their side of it?” How you answer that determines how useful TDD will be to you in your front-end work.
If all you are doing is making data declarations, hookups, directly observing JSON results as fields, round-tripping commands to the back-end to get new JSON results, TDD isn’t going to help very much.
But if you take JSON results, massage the hell out of them, change them without going roundtrip, if your interface uses complex logic you wrote to re-shape itself on the fly? Well, you betcher life TDD is going to help you, and not a little, a lot.
Can one make an architectural rule, “All VIewModel stuff has to happen on their side”? You can. And if you can, I think maybe you ought.
But for most of us, most of the time, it’s too late to make that rule and impossible to enforce it, for causes that include both legitimate and illegitimate explanations.
And in those cases, the majority cases in my experience, TDD-ing ViewModel stuff on the front end will dramatically improve your productivity.
This was an incredibly long ramble, and I’m sorry for it. But it’s a complicated topic, and we can’t just keep alternating our answers to serious questions with either slogans or the ubiquitous “it depends”. So I wanted to share for you what, in my case, it depends on.
There’s tons of code in a UI I don’t write tests for, exactly as true in non-UI. The distinction isn’t UI vs non-UI, it’s “our branching logic” vs “one-liner data declarations that are acted on by their code and gotten once right don’t go wrong”.
My cheeseburger’s getting cold and my beer is getting warm. It’s time for my team-mandated closing ad pitch:
Come help like-minded folks change the software trade! Join me and several others you know in the change-harvesting camerata at: Camerata | GeePawHill.org
GeePaw’s Camerata is a community of software developers leading in changing the industry. Becoming a member also gives you exclusive access and discounts on the site.
The GeePaw Podcast
If you love the GeePaw Podcast, show your support with a monthly donation to help keep the content flowing. Support GeePaw Here. You can also show your support by sending in voice messages to be included in the podcasts. These can be questions, comments, etc. Submit Voice Message Here.
3 thoughts on “TDD on the Front End”
> gotten once right don’t go wrong
The fatal mistake is to assume this property on any external code, framework, library, whatever.
The challenge is in deciding what is and isn’t “reliable” in this way. It’s a judgment call, every time. If we don’t decide that parts of the shipping app can be thoroughly relied on, then we’re stuck with the unsolvable problem of achieving certainty.
I think snapshot tests can be a good way of “pinning” that the thing keeps working as was working.
Comments are closed.