Shipping High Quality JS Apps with Confidence


Shipping bug-less code to production is (obviously) impossible, but still - our users deserve the best experience we can give them. Not only that - if we gain confidence in the way we build our software, we can sleep better at night knowing that it won’t explode in the middle of the night.

In this talk we're going to cover something I call "The Testing Spectrum" - a set of tools, practices and mindset of shipping high quality code to production. From prettier all the way to monitoring, let's avoid your next production incident together!


Hello, everyone. My name is Tomasz Horkomer and today I would like to talk a bit about the way we can ship high-quality JavaScript apps with confidence. But before we move along, I would like to start by telling you a quick story. And this is a story that actually happened. You can see the link over here. I will be also sharing the slides later. And this is a story of how a single company with nearly $400 million in assets went bankrupt completely in 45 minutes because of a single failed deployment. So the story goes like this. A global financial services firm was preparing for a major upgrade of one of their systems. So they wanted to replace an old system that hadn't been used in eight years by essentially reusing a variable. And don't ask me why they had that code in their code base for eight years and why they decided to use a variable, because this is another story entirely. But the story goes like this. They had eight servers and so they only copied the new code to seven of them. So one server still had the very old code, which caused all sorts of problems. And the problems were so severe that they ended up basically trading eight million shares every single minute with no key switch, no documented procedures how to react. And it was an absolute mayhem. And in short, they've managed to lose $460 million in 45 minutes because of a single failed deployment. And the reason why I'm telling you this story is not to make you scared of shipping software to production. It's to convince you and to highlight the fact that quality matters. And it's not only that we care about the quality of our software because we want to be good engineers. Quality of your software can also make or break your business. And the question naturally becomes like, how do we ship quality software? Is there any meaningful way, any single item that we can add to our backlog in order to increase the quality of our software? And not really. Lately, I've been inspired by this paper titled, No Silver Bullet, The Essence and Accident in Software Engineering. And this title is from the 1980s. In fact, it's older than I am. But it speaks, it's completely true, even in 2021. Because in the abstract to this paper, we read that there is no single development in either technology or management technique, which by itself promises even one order of magnitude improvement within a decade in productivity and other factors. And the same goes for testing. There's not a single way, there's not a single technique or tool or whatever we can add to our toolkit in order to improve the quality of our software significantly. It has to be a combination, a collection of different tools. That is why when I think of testing, I like to think about this idea of a testing spectrum. So all of the different tools and practices and the ways we try to optimize the quality of our software lies on the spectrum. So there are tools and practices that are very close to developer and kind of far away from the user. And there are things that are very close to the experience that we are shipping to our users. And a good testing practice is basically a combination of all of those, which by the way is roughly similar to the way we are handling the current global pandemic. There's not a single solution to the problem, but instead by applying multiple solutions, we are able to somehow mitigate the spread of this problem. And again, going back to testing spectrum, we're going to start with the developer. So the first tool, which might be a bit surprising that I would like to mention is Pwittier. And Pwittier is not typically thought of as a tool for testing. But here's the thing. If I have written a code that has some syntax error, Pwittier won't do anything. Therefore Pwittier helps us minimize editor browser, editor cycles. Because whenever I hit save and my code doesn't shift, doesn't move, I am not going to even open up my browser. Therefore I'm able to iterate quickly. I have to open up my browser less often and I'm able to write a better software because I'm able to iterate more quickly and more efficiently create my code. When it comes to other tools that help us write better code, I have to talk a bit about TypeScript. I've been using TypeScript for a while now, and I strongly recommend using TypeScript for medium and large and huge projects. And the reason why TypeScript is useful when it comes to quality and testing is that static types eliminate a certain class of bugs. Most notably, they eliminate bugs like this. Undefined is not a function. That is not a thing that you are very likely to see in production when you are using TypeScript. Of course you will have other bugs, but undefined is not a function is probably not going to be one of them. And a major critique of TypeScript, and for the record I completely get that, is when you are trying to transition from JavaScript to TypeScript, seeing code like this with all of those type annotations is a bit scary to say the least. There is definitely a steep learning curve when it comes to TypeScript, and I definitely get that. But again, TypeScript is great because it helps us also minimize those editor browser cycles, because if I am refactoring a large React component written in TypeScript, as long as TypeScript keeps complaining, I am not going to even open up my browser. I'm going to keep iterating on this code for as long as it takes in order to make TypeScript happy, basically. So I am able to iterate quickly, and when I finally open up my browser in order to play with the UI I've created, I know that a certain class of bugs have been effectively eliminated, so I'm able to write better software and quicker. When it comes to TypeScript, I strongly, strongly recommend using it only in strict mode. In fact, strict mode is recommended by the TypeScript team, but is disabled by default, which kind of makes sense because the vast majority of projects are actually migrating from JavaScript to TypeScript, but still I strongly recommend turning it on. One thing that I would like to emphasize is that type safety doesn't mean that your code is bug-free. What it means is that your code doesn't have any type issues, and that's it. And that's not only it, but nevertheless, you have to think about that you will also have some other bugs, because types are not going to guard you when it comes to misunderstanding of your business requirements and so on, because again, they only eliminate a certain class of bugs, and definitely not all of them. There is no silver bullet. But when it comes to TypeScript, it's somewhat easy in a way, like easy-ish for Greenfield projects, but it's very difficult for legacy projects. If you have a large JavaScript project that you are trying to migrate to TypeScript, it's not easy, it's not trivial, absolutely not. And if you'd like to learn more about TypeScript, and especially migrating from JavaScript to TypeScript, I strongly recommend checking out this book, because some of its final chapters actually talk about migrating from JavaScript. I read it last year, and I think it's great. And move on. We are just getting started on our way, on our journey through the testing spectrum. I cannot talk about testing JavaScript application without talking about testing library. It's absolutely awesome, and I'm a huge fan of it. And this quote that has been said by Kenzie Dodds is basically all that you have to remember when it comes to testing and choosing your testing tool. The more your tests resemble the way your software is used, the more confidence they can give you. Keep your tests close to the experience of your users. Every single time you're trying to think about a new testing solution, think whether it helps us emphasize the experience that we are shipping to our users. There are two things that React Testing Library does amazingly well. First of all, testing implementation details is hard. I've been using it for quite some time now, and I honestly don't remember how to get the internal state of a React component with React Testing Library. No idea, because I never need to do that. And secondly, the API is designed in a way that it helps us build more accessible apps. So to show you an example, the queries that are exposed by testing libraries are basically, you know, you can get elements by role, by text, by alt text. So you are only able to interact with things that users are actually seeing on the page. So there's no testing implementation details. When it comes to React Testing Library or testing library in general, I strongly recommend playing with, because it's an excellent way to understand what kind of queries should you write. To give an example, imagine that you have this very small email form, a label and an input. I would like to test that. So what I'm going to do is a bit of a naive implementation. I'm going to add a test ID, and I'm going to get this element by test ID. If we enter that into the testing background, we're going to see that, okay, so far, so good, it is going to work. Don't get me wrong. But you could upgrade it to get by role in order to write a better query for this component. Okay, fair enough. So I'm going to get this element by role, by accessible role of a text box. Okay, so far, so good. This is awesome. But, and here's the key insight of this, that you could make the query a bit more specific by adding the name option, and this would require to add some markup, though, because your element is not named properly. So here, testing background is inviting us to write more accessible code, because if I add some, a bit of additional markup in order to make this form a bit more accessible, I am able to get this element, this input by an accessible role of text box and a name of email address. And right now, I have effectively built an accessible form element, very small, mind you, but still, it is accessible, and I'm able to also test that. So I've managed to succeed with two goals, with a single function, with a single test implementation. Because here's the thing. Everyone should be able to use a web app. Web is for everyone, and your apps should also be for everyone. You shouldn't be limiting your user base by the fact that you forgot to care about accessibility. And this talk is not entirely about accessibility, and I definitely don't claim to be an expert when it comes to accessibility, but I strongly recommend checking out this course on, develop accessible web apps with React by Aaron Doyle, and the course is actually free, so there's definitely no excuses for you not to check it out. I strongly recommend that. Speaking of users, the thing is that users don't care about your code. They don't care if you're using React, Angular, jQuery, or whatever. They care about the results it provides. They care about the UI, the ease of experience, and so on. And that is why, that is where Cypress comes into play. Cypress is straight up my favorite tool for end-to-end testing. I've been using Cypress for like three years now, and I think it's absolutely amazing. I'm a huge fan of its UI, of its API, and just watching those tests play themselves in the browser is just fun to me. I enjoy watching the results of my tests. And when it comes to Cypress, I have two things that I would like to recommend. First of all, using Cypress testing library, because if you are using React testing library in order to test your React components, if you are using also Cypress testing library, you get two benefits. First of all, you will be writing better queries, because again, you will be strongly encouraged to get elements by the accessible roles, and so on. If you cannot get an element by an accessible role, well, it's time to add one. And secondly, if you are going to shift from writing integration or unit tests to end-to-end tests, there will be much less cognitive shift, because you will not have to think, okay, I'm writing a unit test, so the API looks like this. Now I'm writing an end-to-end test, so there's a completely different API. All those things play very nicely together. Secondly, Cypress has recently added a support for intercept API, and it's quickly becoming one of my favorites, because intercept allows you to have a full control over the behavior of your HTTP requests, and it's supporting both REST and GraphQL, because, well, GraphQL is just REST under the hood. It's not REST. It's HTTP under the hood, when you think about it. Everything is just HTTP requests. And there are a couple of things that intercept API allows us to do. First of all, for instance, if we have a POST request, we can assert that the body of this request is going to include some string. Secondly, if I am sending a POST request, I can also modify this request before it gets sent to the server. So I have a complete control over the network, over stuff that is going to be sent over HTTP. Secondly, if I am going to send a request to the API, I can also assert what is going to be included in the response of that request. So I can test that as well. And intercept allows you to... Intercept can be easily combined with all of the other features and practices that Cypress provides. So, for instance, in this example, I'm able to create a custom command that allows me to stop feature flags in my projects. So whenever I'm going to call my feature flag service, I will be able to get whatever feature flags I'm going to pass into this function. And this is a highly flexible API, and you can do all kinds of things with intercept API. I do strongly recommend checking it out. We are getting close to the user now. It's quite close because Cypress is clicking on buttons, writing text in input fields, and so on. But users are not going to use our Cypress app, Cypress test. They are going to use what we are going to release. So we have to talk about practices for post-release cycle. Because while our app has to be fast, every 100 millisecond delay in website load time can hurt conversion rates by several percent. This is absolutely significant for your team, startup, business, and so on. And secondly, half of your user base will leave a page if it takes more than three seconds to load. But here's a problem, though. It's fast on my machine, right? I am personally using a MacBook Pro. Everything is fast on my machine. But this is definitely not the experience of vast, vast majority of your users. And there are a couple of tools that help us when it comes to monitoring the perceived performance of our users. So there's tools like Century or New Relic or Datadog. And all of them, when it comes to JavaScript development, they do fairly similar things. So for instance, Datadog allows you to create dashboards in order to monitor front-end performance. So you can create dashboards that monitor time to first byte, time to first contentful paint, initial loading time, and so on. You can measure all those sorts of metrics and understand what exactly is going on and what is the experience you are serving to your customers. And secondly, tools like Datadog, Century, and New Relic, they also allow you to monitor the JavaScript errors in production. Because even though you are using the bestest TypeScript typings, well, you are going to have probably some runtime issues in production. Stuff like this always happens in Safari 9 or whatever. And with tools like this, you will be able to understand what exactly is going on, being able to dig in and investigate how do I fix that. The somehow ultimate form of this testing spectrum is not trying to emphasize with the user, it's to become the user. And in my opinion, the best way to do that is to use your own product. And that is called dagfooding in the community, in the tech industry. I don't like the term, but I'm going to roll with it because it's somehow well-known in the industry. So the idea is that it is a practice of an organization to use its own product. And this is a way for an organization to test its products in the real world usage. And for me, this is hugely useful and hugely important. Because imagine that you are working on a food delivery platform. If you don't feel like using your own site that you've built in order to order pizza, because it's slow, it's clunky, there's a huge form that you have to fill in every single time, why would you expect your users to use that? If you are using your own software, you can emphasize with the experience that you are serving to your users. If something is annoying to you, it may be annoying to others. So why not be empowered, take some initiative and just fix that in order to write better, in order to ship better quality software. When it comes to measuring the experience that we are serving to our users, we have to address the topic of testing in production. And by testing in production, I don't mean like YOLO-driven development, put whatever we want in production. But we have to test in production in some regards. Because as Corey Quinn says on Twitter, I call my staging environment theory on account of how many things work in theory, but not in production. And I want you to close your eyes and think about your staging environment. It probably is quite different from production. You have a different database, your database is way smaller because you have like five test users, you could have an API in order to create a user or add, I don't know, $200 to their account. That is probably not something that you have in production. And staging is always different than production. And that is why I strongly recommend using stuff like LaunchDarkly. So LaunchDarkly is a feature flag management tool that does a couple of things and it does them really well. First of all, you can define different feature flags in production, in pre-production, staging, and so on. It's quite easy to use. And secondly, if you enable or disable the feature flag, this change is going to be propagated in a matter of, I don't know, seconds. It's very fast. It's very efficient. So for instance, you get to enable your shiny new feature in production on your test account. You can play around with it, see on production if everything is fine, if it's working against your production API. Next up, you can enable it for only for 0.01% of your user base and then monitor quickly what the heck is going on, whether I'm seeing any errors. Next, you can enable it for 50% of your user base. And by the way, this is an excellent way of also doing A-B testing because you could enable a feature flag for only half of your user base and then ship it to everyone. If you would like to learn more about testing production, I strongly recommend this talk by Talia Nassi. It's available on YouTube and it goes into much detail into how and why should you test in production. I would like to close this talk by expressing this idea that, in my opinion, high quality in software stems from fast feedback loops because if you imagine a loop of develop, shift, feedback, repeat, if you are able to minimize the time it takes to complete this entire loop, you are able to iterate much more quickly. You are able to ship much better software that is more responsive to ever-changing user requirements, to ever-changing market, and so on. But the thing that I would like to emphasize here, this is about minimizing this entire loop, not only the develop part. It's not about shipping whatever to production because then you're going to fail the feedback part because you will have production issues, you will have production incidents, and you will not be able to move on to this big new thing, big new feature because you will be stuck fixing the stuff that you were supposed to ship a week ago. In short, developing high-quality software is always faster than fixing production issues at 2 a.m. And I know that there were quite a lot of things in this talk, but I would like to leave you with one last final thought. This comes from a philosophy of software design by John Asterhout, who said that overall, the best way to reduce bugs is to make software simpler. That is something to think about. If you find your code difficult to test, consider shipping less of it. Thank you so much for listening. Hey, everyone. So good, good. Do you agree if you only had the option to write one type of test, would it also be end-to-end? Absolutely. Because the idea is that if I had to add only one test, I would like to make the test resemble the experience that we are shipping to our users as closely as possible. And our users tend to use our UI, click on buttons, write text and inputs. And the more we can test that, the better. With that being said, if I were to get hired at a company and they would only get a single test per project, I would probably quit on the spot. But this is another topic, I suppose. Yeah, of course, it's a hypothetical question, but it kind of feeds our knowledge of what people think and find the most important type of test to write. So that's good to know. By the way, I have to say I love the term dogfooding or eating your own dog food. I really agree with that. And I used to work for a supermarket and like the home delivery part of that. And I was already a customer of that supermarket. And so I was really happy to be working on that and like kind of killing my own pain points, right? I worked there for a year and never got to kill any of my pain points because I'm not the project manager. But being your own user is really important. So that's a good point. And that beats, in my opinion, any testing tool you can use. We're now going to go to the questions from our audience. And the first question is for Maarten. And Maarten wants to know, what's a good resource to learn about the roles and their accessibility? So there's plenty. So first of all, as I mentioned in the talk, there's this amazing course on on building accessible React applications. And even if you are not using React, I strongly recommend giving this course, just checking it out. Because it talks about so many practices which are completely independent of the framework. Secondly, I strongly recommend following and reading everything written by my friend, Lindsay Kopacz, who has also written a book about accessibility. And it's absolutely amazing. I have learned quite a lot from reading everything written by her. And I strongly recommend that. And secondly, I think that accessibility is a very broad topic. And it's very I would say it's ridiculous to be able to have an entire understanding of all of the different aspects of accessibility. Because there's just so much of it. So what I would probably do and strongly recommend is to go for the low-hanging fruit first. As in, try to use your website with a keyboard. And then if you are not able to, find out what you can add to your app in order to improve that. Because reading the entire documentation is not going to be the most useful use of your time. But if you were to improve your own pain points, again, going back to dogfooding, then you will be able to improve the accessibility of your app. Yeah. A really important point to also mention. In a testing conference, if you ask me, that accessibility should always be top of mind, of course. Okay. Next question is from Janik Olev. What options do you find the most important in Prettier? Enabling semicolons. As in, because Prettier has some of the options. But for me, the biggest value of Prettier is not to think about the options. The fact that I get to add Prettier to a project and I no longer have a discussion when it comes to how do we format this type of function or how we, I don't know, whether we add comma at the end of this JSON object or not. Like, all of those discussions are completely gone. So not only from the testing perspective, as I mentioned in the talk, it's a much faster feedback loop. But also from the experience of a developer who wants to ship stuff to production, not having those endless discussions in Kotlin, it's like, this is going to speed everything up. In my humble opinion, maybe this is a hot take. I don't know. I see no reason not to use Prettier. Yeah. And you shouldn't configure anything. Just use the defaults and everything will fly. And add semicolons. This is actually a way to die. Yeah. Next question is from, I hope I pronounced this right. Will you suggest to use Cypress over Protractor for Angular-based applications? I am a huge fan of Cypress. And I've been using Cypress for the last couple of years. And I have to admit that as soon as I found Cypress, I was not really paying attention to much of the other tools. So my personal take would be to just use Cypress. But this is just a general idea. Use whatever is solving your problems and not whatever is the most popular or solving somebody else's problems. Your problems are your own to solve. With that being said, I love Cypress. Cypress is awesome. You're not getting paid to say this, right? Nope. I mean, they are more than welcome to, I suppose. Well, we'll ask Gleb tomorrow. We have time for one question. Quick question, quick answer. From Ciderman, Criderman, where do you spend most time writing tests in the Pyramid? Slash trophy, slash beehive? Integration tests. The vast, vast majority of it. As it should. Okay, cool. Well, there are more questions from the audience. But if the people that are asking questions want to ask them to you, they can do it in your speaker room because we have no more time to ask questions. So I want to really thank you for being with us here today. I know you had a lot on your plate. You can go out, right? In Poland, everywhere. It's amazing. But yeah, thanks for joining. And hope to see you again soon. All right. Cheers, everyone. I'm going to the speaker's room. Bye.
29 min
15 Jun, 2021

Check out more articles and videos

We constantly think of articles and videos that might spark Git people interest / skill us up or help building a stellar career

Workshops on related topic