DEV Community

Niko
Niko

Posted on

Let's talk about testing

Testing saved my code numerous times. I know that many people think of tests as time-consuming and unneeded, so I hope that by sharing my experience you will learn something new and implement it in your work and routine.

In case you don’t write tests, you might’ve found yourself in the following situations:

  • Going through the flow of a new feature that needs to be developed, more than once, just to make sure that it’s working properly and that you haven’t produced a bug with your last change.
  • Writing a lot of code ( being in the zone ), pushing the feature code like crazy, and then suddenly you spot a problem with your code, a bug that you haven't noticed because you have immersed yourself in your code without getting feedback from your flow. That's a problem that needs to be tracked, however, it could come from 'n' places. That’s when you start going back to each line of code to see what was changed or add/make some modifications to see if it's working and when you get it done... 🤯 you hit another error from a completely different place. Sometimes the circle could be really big, as it strongly depends on the size of the feature. That's the moment when you just want to start over. I have been in this situation a few times and it's frustrating.
  • Reproducing some bugs may require complex interaction flow. For example, you might need to fill in and submit a form, then navigate to another page where you fill in another form, and that's when the bug occurs. You’re aware that this may cause a problem as you most likely are going to go through the process multiple times after you make some small change in your code. . And let's not forget when you mistake the step order or some specific information that you need to provide.
  • The situation when you have to merge your work with your colleague’s and you have to test that they work together and that you are not breaking anything. This might result in a two-hour call with your coworker because you don't know the flow and the expectations, time you both waste to see if everything is working smoothly separately and combined. And let's not forget the moment when óne of you has forgotten what should happen in a specific step and you have to go back to the PM or the specification and waste more time.
  • A scenario where a feature that has existed for months/years breaks without anyone realizing, maybe not even a whole feature, but a small specific detail of a feature - an edge case

That’s why tests can help us:

  • Get fast feedback for our code changes
  • Minimize the unnecessary calls and meetings
  • Show us when some part of the app is not working
  • Verify easily that the known bugs are fixed
  • Get some good technical documentation ( we do when the tests are structured well )

My journey with testing started about two years ago. In the beginning, I was very skeptical and didn't want to do it because I didn't see the point of investing time as it is very time-consuming. I wasn't even thinking about testing. However, my attitude about testing and the opportunities that it gives me changed over time. I will keep doing it because it made me think more. The tests for me now are like a notebook for my thoughts.

Testing is half code review 🧐

Let's think about the process of doing a code review. In the process of review, you are trying to be focused and look around if something is missed or wrong or maybe unnecessary. So testing gives you the same if you think about it - before you code you can step back, start playing around with what you expect to get/see, you can even think about different edge cases that the feature could get into. Also when you write a test you need to write a good code, otherwise, the testing could be painful, that’s how your code gets a better structure too. There is another bonus from this - you are going to have documentation out of the box. You can just open some test files and start looking in the test cases, get familiar with what the feature should do, and also see what is missed. In the end, the comments on the code review might be fewer.

Developer and QA 👀

We know that the feature should fulfill some set of requirements for the so-called user stories. I love the user stories but they don't help you with exceptions and errors or with the custom sequence of actions that are not described in the story. The tests can help with all of that because you can step aside and think about the events that could happen, moreover, with a good test structure you can find some really important missing points. It's much easier to look at the test descriptions than to go over the code and see what it covers and what needs to be added. Of course, we can't capture all edge cases and think of all possible scenarios, I agree with that. But rest assured that your QA will find the missed things and come back to you with a request I’ve never worked with a QA before my current job and I was interested to know how and what do they do and how it can be beneficial for us. I already know that when I have some sophisticated feature it's just not possible to think about the multiple actions that could happen and the states within it. This is the time when the QA should shine and release their imagination. After I started working with a QA in the team I began asking myself when do we need him and whether it’s always a good thing to have him in each feature. Those questions have been influenced by the fact that the ticket could stay for a few days in the QA section in the Jira board and this could create a blocker for the developers. I think that if your specification is pretty good and the app doesn’t have some tangled flows, the QA can work on something else and bring more value to the project. If you think about it the QA team is one more team in the project, a few more opinions that could prevent the specific thing from going to production because of the additional 1.5px on the left side which is not cool. I know that sometimes this 1.5px might be important but there are scenarios when you need to discuss it and you just lose time. I don’t a lot of experience working with a QA so the only thing that I have noticed is that developers can get lazy and rely on the QA team to find a bug and report it. However, if both of the teams are concerned about the product quality equally then the QA could focus more on the crazy paths that the feature could get into and reduce the amount of time they spend on the basic ones.

Code coverage 🗒️

My opinion about the code coverage is simple - use it to see if you have missed something important but don't go with the idea that you are going to sleep calmly when you have 100%. You can try with 100% but you still won't capture all of the user event combinations and yet you’d have to maintain a lot of tests and invest a lot of precious time in them. Just don't forget that the most important thing is the assertion, if you have the right assertions then you will be fine. I say this because you can get 100% coverage pretty easily without any assertion in your code. So when you see the coverage report use it as a reference to see if something important is missed and fix it.

Snapshot testing 📸

Snapshot testings are fast to generate but if you create a big snapshot the chances to find a bug are really small. So keep your snapshots small and verify them every time before updating the snapshot. I have been using snapshots mainly for styling changes and I remember very well these big chunks of generated code where you get lost every time ( it doesn’t go well ). If the styling is something simple that can be done with inline styles then you can try with a few assertions. However, if it's something complex then I would go with the snapshot ( not the best option but still can bring some reliability ). I mentioned inline style intentionally because if you go with the approach to test the classNames in your unit test assertions then you won't get too much out of the test since you don't validate anything about the styles, it's just some implementation detail that can change at any time. If you are using css-in-js some jest packages can help you with the unit tests and the generated snapshots. There is also another tool that can bring you a very high level of confidence - visual regression tool ( example: https://www.chromatic.com/ ). So before you start your project think about how you are going to do styling within the app and how to test it as it is very important for your code

Visual Regression testing 🎭

Visual regression tests are like snapshot tests but the output is an image, not a string. Trust me, it's much easier to look over an image to see the difference than to go over lines of code to understand what the discrepancy is. So if I need to test any visual change over time and I have to choose between visual regression test and snapshot I would go with the reliability that visual regression test gives me.

Stories ( Storybook ) 🧙

The main idea of the stories is that you can play around with the UI components in isolation - you can test them directly through the UI and they could be public, not just for the developers.

Before I got my hands dirty with writing them I was a bit skeptical but after a few months when we had a lot of components I started feeling the benefits of stories. I guess if you are building some complex UI, you can find them beneficial but if you go with a few components then it could just slow you down. Also, I like the stories because it helps in situations where a UI component has events and states within it that could only be seen if you follow a very time-consuming flow. So here comes the power of the stories - the visual state of any component within your app just in one click which can be done by anyone - developers, designers, PM, QA, etc. It's a really powerful tool that can simplify your work when you have multiple UI components. The PM can even do acceptance tests on them. Stories could be used as a shared notebook where the designer should be explicit without adding too much because it can overflow really. Having a lot of components can just slow down our development process so this on the other hand can affect the business. As you might’ve already noticed everything has a price and this has too. Before you start putting an enormous amount of components in the stories, remember that you have to maintain them as well. Don't forget to write down some documentation when you write your stories just to let the others know how they can use the component. For the documentation, you can use the storybook addon for docs that is helpful. Have a look at their latest version here.

Summary 🤙

Before we move on let me just make a quick summary. The main idea of the testing is to secure the value the software creates. So whenever you start writing any kind of test ask yourself "What does this test protect me from?" and this is the moment where you should decide if it's worth writing it or not. There are different types of software and people so there is no single answer for the question "What should be tested ?", you should decide it within the team - have a testing strategy that fits your needs and product/s.

Whenever you start thinking about what strategy you will follow, don't forget to revisit it. Have a thought on what you have made until now and see if it could be better.

  • Think if you have the appropriate level of confidence in your application. Maybe you have useless tests that are not bringing the needed confidence and they just act as a burden.
  • Can you use the test as documentation about the use cases and error states? When you have a look in the test suite do you get the idea of what is going on or does it make you even more confused?
  • Think about the structure of your tests - maybe it doesn't scale well or maybe you are repeating yourself too much and something could be extracted.
  • Maybe you have gone too crazy with the tests and now they slow down your development - that's the moment when you wait for 30-40-60 mins even more to get the pipeline. When you are in a situation where you are not sure if you need to write a test or try not to write and see what happens you can always go back and add one more test.
  • Do you have to make a lot of changes within your test when the application code is changed? - maybe you are focusing too much on the implementation details and not enough on the interface that the client use.
  • Decide whether you need all of these unit tests - maybe you have already validated some of them with the integration test or e2e test and you have the confidence. Yeah, I know that we are focusing on the reliability of the app but we also want fast feedback and good maintainability.

Two more things that I couldn't fit within the post 😅

Don't test your project dependencies because they have already been tested out. For example, I have been in a situation where we were testing if the highcharts.js is working properly and this was captured with a lot of tests that need to be maintained. Instead, we could’ve just tested out our data generators that are producing the data that we want and maybe use some tool for visual regression to verify that the data is correctly visualized. Test the things that are yours, test your business logic, and don't waste time on the dependencies code.

Don't mock everything that comes as a blocker when you start writing tests. Just mock what you need, nothing else. I know that when you open your front-end project you have n-th dependencies and that's normal. Just remember that every time you mock something you are saying that this package won't change its behavior and it will be true if you don't update your package. In case you don't upgrade them it's all good but if you do upgrade them then you can have unexpected behavior within your app because of an out-of-date mock which will say that the test is fine when it's not.

Overview of the testing pyramid

It won't be a blog post about testing if I don't mention the pyramid

E2E Test

It interacts with your application exactly like users would: by interacting with your page’s elements through a real browser. Because these interactions depend on the client sending adequate requests and the server providing adequate responses, it covers both your frontend and backend.

Ask yourself the following questions before you decide to use the e2e test for your feature:

  • Is the feature critical for the client? ( this should be decided not only by you! )
  • Is it cumbersome to validate the flow manually? How many steps do you need to take to complete it? Do you have to set up something in the DB or cms etc.?
  • Are you going to write the test fast?
  • Is it something that you have to test frequently?
  • Are there some edge cases that you should think about before you start writing the test?
  • Is the framework that you use for the e2e test a blocker in some of the states?

Example of a valid e2e test:

Let's imagine that we are building an educational platform and we have the feature where we can create a course with resources in it.

e2e-test-example

As you can see from the image we have a form from which we can go to four different screens. We can add resources from internal libraries or we can scaffold our learning materials. The flow should fill the form to add resources, store them and when we go back to the main form visualize all of the added resources, ensure the user can delete the added resources, and in the end just submit them. This is something important to the business because if the instructor is not able to create a resource then the app will be empty. The flow is not simple so for sure you don't want to reproduce it whenever you make a change. Here we can easily cover the expected path with the e2e test and be secure that the happy path is correct and it's working. When I finish the testing I will have the needed confidence that this flow is working properly. I might have some integration and unit tests for some parts of the flow but for sure I will do an e2e since it's critical for the business.

Just want to point out here that if you don’t test the front and the back then you are not doing an e2e test. I have been in a team where we were naming tests with fixtures e2e tests but they were nothing more than integration or unit tests. At the moment when you start mocking you are not doing e2e.

Integration test

These are tests that are validating if different fragments/units are working together. Whenever you write a test try to follow the official documentation suggestion - write the integration test as high up in the component tree as necessary to get reliable guarantees. But don't forget that if you render too much your test will get slower and you might also need to mock too much stuff and lose the reliability that you have been looking for.

Example of a valid integration test:

integration-test-example

As you can see we have a list of our courses where the user can look at them and choose the right one for their preferences. This is something pretty simple and we can capture it easily with an integration test which is going to verify that the list is rendered properly and that the pagination is working well with the list.

Unit test

If the e2e test is the organism and the integration test is the molecule then these tests are the atom. They are fast to run and quick to write, but they cover only a small part of your application, and the guarantees they provide are weaker. Just because functions work in isolation for a few cases doesn’t mean your whole software application works. This is just a simple course card where the user can see the image, title, description, etc. We need to validate that the card itself is working properly, that it is visualizing the things that we want to be there. So when I have the test I can be sure that my card can be integrated without problem within the app. Another example could be just a function that does something specific ( business logic, transformator, etc. ) and you need to verify that it's doing it right.

Example of a valid unit test:

unit-test-example

So this is just a simple course card where the user can see the image, title, description, etc. We need to validate that the card itself is working properly and that it is visualizing the things that we want to be there. So when I have the test I can be sure that my card can be integrated without problem within the app. Another example could be just a function that does something specific ( business logic, transformator, etc. ) and you need to verify that it's doing it right.

Have fun and I hope you have learned something new today. 🤚

P.S: Don't forget that you need some practice so that you can refract information through your prism. So give it a try. If you don't have it as a practice in your team you can always try with a personal project.

Latest comments (0)