Video Summary and Transcription
This Talk explores the intersection of accessibility and test-driven development (TDD) in software development. TDD is a process that involves writing tests before writing production code, providing a safety net for code changes. The Talk demonstrates how to apply TDD principles to real-life examples, such as filling out a form, and emphasizes the importance of user-centric testing. By using atomic design principles, code can be organized in a clean and easy way. The Talk also discusses the use of labels and test IDs in tests for improved accessibility.
1. Introduction to the Talk
Welcome to this talk. My name is Rita, and I work in the SDC, the software development center for the Volkswagen Group in Lisbon. In my free time, I like to game. If you're ever in Lisbon, please do drop by our building.
Okay. So thank you very much. Welcome to this talk. And let's get started. So my name is Rita. I'm a geek at heart. I really love comic books. I have a son named Pedro. He's three and a half years old. I recently since we got back from the pandemic, I started cycling. And when I go to work, I usually do so by bike. In my free time, I like to game, which is nice. I work in the SDC with these amazing people, which are the ones that are kind of making noise. And the SDC is the software development center for the Volkswagen Group in Lisbon. This is our building. It is really pretty. If you're ever in Lisbon and you want to visit, please do drop by.
2. Accessibility and Test-driven Development
So for today, accessibility. It has been talked about in the previous talks. The first definition that you find in the dictionary for accessibility is something that is capable of being understood or appreciated and reached. Test-driven development, what is it? In a nutshell, it's the ability to convert software requirements into test cases before you do any line of production code. So, given accessibility, given test-driven development, how exactly do these two match and why exactly is it so important to combine the two of them? Let's come back to accessibility. For us, developers, accessibility is the power to enable as many people as possible to use the software that we use, to empower people to use the things that we built. Accessibility is for all of us. Including us. Especially us. How many of you have issues with the keyboard? And accessibility gives us this. Coming back to the test driven development. So, my first contact with TDD, it was through this book.
So for today, accessibility. It has been talked about in the previous talks. And there's a big reason for it. So when you think accessibility, you usually think about people with disabilities, or something that can be accessed by people with disabilities, or something that is adapted for people with these set disabilities. However, that's not all of it. The first definition that you find in the dictionary for accessibility is something that is capable of being understood or appreciated and reached, which is big. Let that sink in.
Test-driven development, what is it? In a nutshell, it's the ability to convert software requirements into test cases before you do any line of production code. It is quite simple and just to get a feel of the room, how many of you have done test-driven development? OK, cool. Then I will let you go. Then I will fast forward. But for those of you who haven't, test-driven development is ‑‑ sorry.
So, given accessibility, given test-driven development, how exactly do these two match and why exactly is it so important to combine the two of them? I hope that by the end of this you will have a better idea of it. Let's come back to accessibility. And a funny thing that I discovered. So, between the A and the Y, there's 11 letters. And that's why it's usually referred to as LE or A11Y. I kind of didn't know that. But once I did, it became a fun fact. For us, developers, accessibility is the power to enable as many people as possible to use the software that we use, to empower people to use the things that we built. We don't want to build things to be put on a shelf. But it's not only for people who are in a wheelchair, who have some crutches, who have a wheel cart for the baby. Accessibility is for all of us. Including us. Especially us. How many of you have issues with the keyboard? If we can spend all of our time just using the keyboard, that's pretty cool. And accessibility gives us this. Coming back to the test driven development. So, my first contact with TDD, it was through this book. It is done for Java.
3. Introduction to TDD
TDD is composed of a red cycle where you write a test, make it fail, implement production code to make it pass, and refactor if necessary. It provides a safety net for code changes. TDD can be applied beyond software development, such as peeling a potato, using the 3A pattern: arrange, act, and assert.
It is written with Java examples. It's quite comprehensive. It's super, super well structured. And it gives us the basis for what is TDD.
So, in a nutshell, it is composed of that magical round that I showed you. The first part is the red cycle. Or the red part of the cycle. You write a test. You make it fail. You implement just enough production code to make the test pass. If needed, you refactor both the test and your code. Sometimes things get done sort of right at first, and there's no need to refactor. Sometimes there is the need to refactor. Be mindful of that. And with the cycle of TDD, you will be able to have always a safety net on the changes that you do to your code.
But for sure, you can say, okay, but that's very easy when you're doing back end development. What about front end development? I don't know exactly how things work with front end. How do I test things? How do I need to click on things? Fine. So, think about TDD not only as a methodology for software development. Think about it as a way of applying things. For example, a potato. If you want to peel a potato and if we want to create a user story that goes along in the lines of the user wants to be able to peel a potato. If you want to go straight away into peeling the potato, you're going to start thinking things like, am I going to do it with a knife? Am I going to do it with what type of a knife? Should I use a potato peeler? Should I use a potato peeler that is one of those that you do like this and that the seesaw is like this or is it like this? It doesn't really matter. Should you boil the potato and then peel it off because the outside gets off really easy like this. You should peel it from the top to the bottom or from bottom to top. It doesn't really matter. Why doesn't it matter? Because at the end of the day, what you want to do is to have peeled the potato. So you can kind of convert this into a test. So you follow the 3A pattern. You arrange, act and assert on your object.
4. Filling out a Form with Test
You get a potato. You peel it. It is peeled. It's very simple. But what if we take a real-life example? Imagine you have a form and are asked to fill in some personal data. You start filling it up, but end up going the wrong way. To complete the form, we need to write a test to cover the user journey.
You get a potato. You peel it. It is peeled. It's very simple. You focus your way of cooking and you will not get lost in the details. You will go straight to the point and do just what is needed to peel the potato.
So this is very fine with the potato. But what if we take a real-life example. Imagine that you have a form and imagine that within, let me find the mouse which is here, let me find that within this form, you are asked to fill in some personal data. You have found the string that says enter your name, you start filling it up. You do tab, tab, tab for populating the form.
Will you go or will you not go? Yeah, okay. We will go. Oops. Wrong way. This is not the way we want to go. Will we go to the hangout day? Yes. For sure. How? By boat. Okay. If we go by boat, all of the form is filled. Okay. We need to click. Okay. I need to click this button that says something. Cool. The flow is done. I've just told you a story. This is the user story that we will have to fill out this form. How can we do this? We can write a test and cover this, cover this journey of this user which will be us.
5. Organizing Code with Atomic Design
Let's explore how to go from a form design to a fully organized project with atomic design. By applying atomic design, we can organize our code in a clean and easy way. Let's start with the text input molecule and write a test to ensure it has the required elements. Once the test passes, we can proceed to write the necessary code.
Everything that I've told you, it's mapped out to this pseudo code or to these comments. So let's get started. How exactly will we go from a form that in this case some designs or some visual interfaces to a full blown project, to a project that has, it doesn't. I think it should have a it does. How do we go from a form that has, from designs to a product, to a project that has the source folder, that is organized with small stuffs, incremental stuff, that has tests that will eventually have an end-to-end to back up the user journey.
How can we organize things? We can do so by applying a design pattern that is atomic design. You can be fooled by the name where it says atomic design, so it must be targeted for designers. It is. But we can leverage the power that atomic design provides and apply the same pattern in the software that we built. So, when we are building the software, we organize our components by complexity. We have the simplest elements as possible. HTML tags that will go as atoms. You start combining them to form molecules. You start combining molecules together to form organisms, and then you have the last pair in the latter which are the templates and the pages. You can kind of think of pages as instantiated templates. I'm not going to go into detail on the atomic design part because on its own, it's a huge topic to go on and about. But, I just want to give you the seed and plant you the idea of it is possible to organize our code in a very atomic way and a very clean and easy way for onboarding new people.
Let's get started with something. The text input molecule. From the design, the text input molecule is something very simple. It has the text, your name, and has an input box. Okay, let's write a test for that. The test for that will be something that it has the title and it has the input. We will be using web components for the demonstration. We will populate or generate an HTML with the said web component and within the HTML, we will expect to find a paragraph, kind of, with the text that we want, and with an input. We run the test, and when we run the test, it's from the red side, because of course there is no production code to back up the test that we just did. So, let's extend. Let's write just the right amount of code to make our test pass. Which is just this. Nothing else.
6. Adding Flexibility to the Text Input
The test passes, but this is a crude implementation. We want to add flexibility by allowing custom titles and input control. The test fails initially, but we update it and the test passes. We've added two more attributes for the text input.
Nothing more. And the test passes. But this is a very crude implementation. This is very hardcoded, so we want to give it some flexibility. If we want to give it some flexibility, and while we're updating the test, let's also give it some ability to control the values that we're putting there. So the title, it's kind of useful that people can reuse the molecule, so let's have it as a custom title. And let's have the control of the input. Fine. The test fails, because there is nothing there. Fine. Let's put it there. We put it there, it's there, the test passes, we're good. We can continue. So we've just added two more attributes for the text input.
7. Building a Form with Test-driven Development
With test-driven development, you get to do what is needed to make your use case pass. We've got molecules. Let's have a form. Let's write a test for a form in which we can ask for a first name, we can ask for a last name, and since it is a form, we will have a button to send the information. The test will not be able to distinguish between the first text input molecule and the second text input molecule. But we know there are two of them, so we can make it work. If by any chance in the country you're rolling out the form for, people usually use the first name before and the last name before and the first name afterward, your test is going to fail. The contents of the form, the way the form is built, it still works.
But as you know, there are lots and lots of stuff that can go into an input. Do we want to build them all? Do we want to start, I don't know, with each and every one of them? No. There is no need to do so. With test-driven development, you get to do what is needed to make your use case pass. You do not more, and not less.
We've got molecules. The text input. So, let's start putting some stuff together and see how we can progress with the form. Let's have a form. Let's write a test for a form in which we can ask for a first name, we can ask for a last name, and since it is a form, we will have a button to send the information. When we run the test or when we write just enough production code to make it pass, this seems decent. We have two molecules. One says first, one says last. There's the button there. Fine. Move along.
Not move along because each of the text input, it's going to render one P. So, the test will not be able to distinguish between the first text input molecule and the second text input molecule. So, this kind of sucks. But we know there are two of them, so we can make it work. We can say it's an array. The first element of the array is the first name. The second element of the array is the last name. So, it's cool. It's cool. We got this. We totally got this. However, if by any chance in the country you're rolling out the form for, people usually use the first name before and the last name before and the first name afterward, you're kind of bummed. Your test is going to fail. The contents of the form, the way the form is built, it still works.
8. Shifting Testing Approach
Let's make a shift in the way we do our tests. Let's have our tests resemble the way the software is being used. When we do so, it will give us more confidence in the way the software, in the tests that we are doing. This is not my quote, this is Ken Seedot's quote for the testing library.
It shouldn't have failed. So, how could we fix this? Okay. We can probably, probably, if we are sleek enough, we can go to the form. We can try to find the elements. We will right click, inspect. We will look for what the hell is that one? Okay, I got the ID, so I can go and I can tweak the code, and that's it, and I have the test and it's all good. Please bear with me, it's going on again. The only people who do this are us, the nerds. Users don't do this. Users will look for something. Users will look for the text that is written in the page that is being displayed in front of them. So, let's make a shift in the way we do our tests. Let's have our tests resemble the way the software is being used. When we do so, it will give us more confidence in the way the software, in the tests that we are doing. This is not my quote, this is Ken Seedot's quote for the testing library.
9. Improving Form Accessibility and Refactoring
From now on, all the test development will follow the testing library. We will add labels to the form elements to provide information and improve accessibility. We'll update the text input molecule to accommodate labels and ensure accessibility. The testing library helps us identify and fix any issues with our tests. We can now refactor and extend the code to include more personal data fields in the form.
From now on, all the test development will follow the testing library. How does that translate into the test that we have? So instead of having just the P, let's start with the label text. We will have a label for last name, we'll have a label for first name. We will also give a news, the advantage of the button being a specific HTML element, and we'll click it, and that's pretty much it.
A small note about labels. People tend to mock me because I put labels on everything that I do. Why is it? Because it is a small piece of paper that is attached to an object, and it gives information about it. It stands for computers, it stands for cables, it stands for anything. In particular, for web development, a label in an HTML element will represent a caption for an item that is on the document. So you will be able to access it. You will be able to reach it within your DOM.
So let's start typing on the forms that we have. When we do so, we can increase the complexity of the test that we just built. So we will also be able to give the semantic meaning of having a form because I didn't have a form before, I just had loose elements spread out in the document. So let's have a form within the document. Let's have the first name, let's click it, let's type things on it, let's use and mimic the interactions that the user will do with the page.
But this gives us a problem, which is the input molecule, the text input molecule, it was not prepared to receive labels, it was not prepared to receive more information than the one we already gave it. So we'll need to come back, update the text input molecule, and always having in mind accessibility. So all of the queries that you're doing, all of the tests that you're writing, you're doing so, thinking about how will I be able to access this, how will I be able to reach it? The testing library is very helpful when it comes to telling us what is wrong with our tests. So let's just fix it. Nothing, nothing more, nothing less. We fix it. It looks happy and content with it. And all the tests pass. So we were able to do not only some refactor, but also some extension of the code that we already had. It's good. We already have that form with the first name and last name and then the button. But if you remember from the form, it will request more information about personal data. So maybe it is refactoring time. It should be fine.
10. Creating Personal Details Organism
Let's create the personal details organism. You run the tests, refactor the code, and improve the accessibility. Test-driven development gives you security while developing your app. Once you put everything together, you can build, bundle, and ship it. You will have confidence in the code you're building.
Let's create the personal details organism. And while we're doing so, let's give it even more meaning. Let's say that it is the first name, the last name and the email. This is personal information. All of these inputs, they can be grouped within a field set. They can be given semantic context. They can be put in the same bag so that you can reach it in an easy way.
You run the tests. You will then refactor the code. You will run the tests and everything will still pass. So you've done a huge refactor. You've been able to improve the accessibility of your code. And nothing went wrong. So the cycle of the test-driven development will give you security while you are developing your app, thinking about accessibility first.
For the end of it, you will eventually continue and develop the rest of the organisms and the molecules. You will develop the checkbox, the input, the radio buttons. You will take care of building up the dropdown, selecting it, et cetera. So once you put everything together, you can go up one layer, instead of writing or hard coding the HTML that you're doing, you can actually build it, bundled it, ship it, and then do some end-to-end tests on it. Always from the point of view of the user.
If you do run the thing with Cypress, you run the test. A browser will eventually open. A browser of your choosing. You get to configure it. The form, if you have more than one test, which is typically a use case for the user, it will run. In every scenario you have, you will be able to transform into a test that will get executed each time you ship your code. So you will have security in what you're doing. You will have confidence that the code that you're building, you are doing it using accessibility first and using just what is needed to make it work.
So to conclude, you have accessibility. You have test-driven development. It is a perfect match.
Maintaining Labels and Test IDs in Tests
Because you can transform your user stories into tests that will drive the implementation of websites, and also the tests themselves, that are capable of being understood and appreciated by all of us. So let's jump into the Q&A. The first question is from anonymous. How would you maintain the labels in the test? Doing it dynamically would kill what the user looks for. Maybe you have them in a constants file, and use them in the component itself, and import them also in the test. That's what anonymous means. And what are your thoughts about using test IDs instead of labels? As a user, do you need test IDs? Or do you need them as a programmer? As a user, I would say, you never need any. Do you develop code for programmers or do you develop code for users? Well, I actually make code for developers. Ok, fair enough. But in your scenario, you're right.
Because you can transform your user stories into tests that will drive the implementation of websites, and also the tests themselves, that are capable of being understood and appreciated by all of us.
So that was it. Thank you very much.
So let's jump into the Q&A. The first question is from anonymous. Very good. How would you maintain the labels in the test? Doing it dynamically would kill what the user looks for, basically. Doing it dynamically. So now you hard-coded the strings, the labels? Yes. Maybe you have them in a constants file, and use them in the component itself, and import them also in the test. That's what anonymous means. Okay, could it be that anonymous is called Metin? No. Okay. Who knows. That kind of looks a little bit like if you were to have translations, and that you need to be able to use something that is not hard-coded. In those cases, you kind of have to abstract what you're putting in and what you're feeding to your component. So add another level of... I don't want to say complexity, but add another level of abstraction to your code, and have that be the one that's responsible for taking care of the dynamically changing thing. Maybe overwrite in the test, overwrite the import, so that it's hard-coded and it's always the same. Exactly, it's hard-coded, it's mocked. And eventually it's mocked, for instance, if it is a translation. You want to be sure that the translation has been called. Something simple, that allows you to be sure on what you're doing. It just needs to be constant, right? So first name could just be string one, last name can be string two. For instance. Alright, thanks Anonymous. And what are your thoughts about using test IDs instead of labels? As a user, do you need test IDs? Or do you need them as a programmer? As a user, I would say, you never need any. Do you develop code for programmers or do you develop code for users? Well, I actually make code for developers. Ok, fair enough. But in your scenario, you're right.
User-Centric Testing Approach
You always have to think about who will be using the software you're developing. Test as much as possible like the user is using your application. Write your tests as if you're a user. Focus on the labels and inputs.
You always have to think about who will be the people that will be using the software that you're developing. If that person needs a test ID, that person needs a test ID. But ask why the person needs the test ID, not just because. Yeah, and it's like you showed with Kenzie Dodds' tweet, his quote was just, try to test as much as possible like the user is using your application and try to write your test as if you're a user. And if you're a user, you're looking for that label and you're clicking that so that it will focus your inputs. Exactly, exactly.
All right. All right. Well, that's the time we have Rita. So thanks a lot. Thank you. Anymore questions for Rita. Rita is going to go to the speakers booth now and on Sli.do you can also ask your questions.
Comments