Let's Talk Testing: 4 quick lessons on the philosophy of testing
Testing is an area of front-end development that has improved rapidly in the last few years, but there it can still feel overwhelming.
But there’s still a gap. It’s extremely hard to find information on the philosophy of testing. What to test and why. How much is enough? What type of tests should I be writing, and when does it fit into my process?
There are a few great articles out there. I really like this one by Max Kanat-Alexander and while I’m not personally a practitioner of TDD I found this article on how TDD fits into a broader system to be enlightening.
But there aren’t many.
That’s why when I recently had a conversation with Christopher Hiller (maintainer of Mocha) and Nick Nisi on JSParty #70 that ended up talking a lot about testing philosophy, I thought it would be worth pulling out some of the key points. Here they are:
1. Testing best practices change over the lifespan of your project
I think this is one of the biggest sources of conflict and confusion about testing - the best practices change a lot depending on what your project looks like and what stage it is in!
As Nick said:
I think that too many tests are especially bad if there’s a lot of flux going on in the code, because then things just do break all the time, and especially things like unit tests, or a lot of the tests that we end up writing right now, testing the output of the virtual DOM, and comparing that, and making changes to the state somewhere, and then diffing that with what we expect the virtual DOM to look like… Things like that.
Early on when your project requirements are changing rapidly, for many cases focusing too much on testing will be pure overhead. As things stabilize and mature, more and more testing becomes appropriate. As I noted in the episode, this bears a lot of similarity to the type of refactoring and refinement you’ll do in other parts of code as well:
I think it also just ties into “How much do you wanna worry about making your code dry?” We had an episode 4-5 months ago where I had a conversation with Michael Chan (chantastic) from the React Podcast, and he had this whole talk about “dry code is brittle code.” If you’re changing your code a lot, the more you dry it out, the more you add lots of tests, the harder it is to change.
This ties into Chris’s point about Mocha being now a more mature project. It’s more mature, it’s changing at least on a feature level more slowly; that would imply that some of these more traditional code quality metrics like test coverage and how dry it is and things like that may actually be more valuable and applicable than an early-stage project where everything’s in flux.
2. Emphasize functional tests over unit tests
Unit tests are “easy” to write, and in some cases can be extremely helpful in how you think about your code, but they miss a lot and what they do provide can often also be covered via functional tests.
Straight from the Mocha-master Chris:
And then we see that those unit tests don’t necessarily mean too much if the whole end-to-end process is not coming together. You could have 100% code coverage and your app could be 100% broken. But in Mocha, for instance, I’ve come to see that we don’t have 100% unit test coverage… And that’s fine, because we have so many functional tests where we invoke Mocha from itself, and test the output of Mocha. Those are incredibly valuable, and it makes me think – I could see unit tests from looking at TDD, and there’s people who love it, people who hate it, people who fall somewhere in-between… I wouldn’t see the value in going back and adding a ton of unit test coverage to a bunch of stuff in Mocha when it’s already very well covered by the integration testing.
3. Have the same teams writing your tests as your code
While it can be extremely helpful to have people on your team with an expertise in testing, it’s better to have them integrated in the team rather than a separated QA team. And ideally everyone writing code is writing tests.
Nick on what happens when you have a different team writing your end to end tests:
Those tests often are very brittle, if it’s not the developers thinking about it, I think… Or at least the developers who were working on those specific features, or that specific part of the app, if it’s more of a team writing those tests; I’ve seen those tests be more brittle and break quite easily… Which is always scary, because then – the first thing that I think of whenever an end-to-end test breaks is “What went wrong with the test?”, not “Is the test broken?” or “Is the text actually showing me an error, or is the test just broken?”
4. Even manual tests can help when refactoring legacy code
As developers we tend to want to automate everything. And generally, that’s a good thing. But there are times - for example when refactoring crufty legacy code - where getting an automated test in place is a mammoth task. If you’re trying to refactor it, even having a well-documented set of manual tests can help you get some safety guards in place without taking weeks before you can touch the code.
Yeah. So if your legacy app doesn’t have those functional tests, it may be really difficult to do that upfront… Especially if your code is spaghetti, or not written in a very testable way, and then maybe you want to… Oh, boy. Yeah, so you’re afraid to move forward with the modernization unless you get these tests in place; and it’s painful to put the tests in place, because the code is bad. [laughter]
You can even do manual tests. “What is the functionality that this is even trying to support? If I test through it right now physically, what does it do? Okay, I wrote all that down; I wrote how I tested it, now I change something - does it still freakin’ work?” Because ideally it’s all automated, but there’s some code that writing a test for – you’re gonna spend days on that.
Ok, that’s a rundown of 4 quick lessons about the philosophy of testing. Do you have more? Drop them down below in the comments!
Interested in the people behind these recommendations? Want to hear their thoughts on how other parts of development change by project type, and some great lessons learned about working with legacy code? Listen to the entire episode right here 👇