“These tests should never have been written. They provide no or little value.” -ME
Testing code as been described as an "art form." It is, but it should not be. There are many good patterns that should be followed when writing tests. There is even a lifecycle of tests that should be paid some attention. There are also many BAD patterns that developers should be aware of so that they can be avoided (or cleaned up).
This session will provide a series of examples of bad front-end tests and how to write them correctly.
2. Confidential & Proprietary
www.leadingedje.com
Confidential & Proprietary
www.leadingedje.com
A QA Engineer walks into a bar.
● Orders a beer.
● Orders 0 beers.
● Orders 999,999,999,999 beers.
● Orders a lizard.
● Orders -1 beers.
● Orders a ueicbksjdhd.
The first real customer walks in and asks where the
bathroom is. The bar bursts into flames, killing
everyone.
3. Confidential & Proprietary
www.leadingedje.com
Confidential & Proprietary
www.leadingedje.com
What Are Unit Tests?
● Unit Tests exercise small parts of the application (code-under-test).
● … in complete isolation.
● … actual behavior versus expected behavior.
● Unit Tests should be fast, simple, and stable.
● Unit Tests reflect the specifications.
● … can act as documentation.
● Unit Tests are a safety net.
● … provide immediate feedback about code changes.
● … find and fix bugs earlier.
● … contribute to higher code quality and better architecture.
● … faster detection of code smells.
4. Confidential & Proprietary
www.leadingedje.com
Confidential & Proprietary
www.leadingedje.com
Talk Details
Code Repository: https://github.com/bob-fornal/what-to-avoid-when-writing-unit-tests
I can be found at …
● https://linqapp.com/conference
● Twitter: @rfornal
● Articles https://dev.to/rfornal
● LinkedIn: https://www.linkedin.com/in/rfornal/
Social Media Project …
#100DaysOfCode or #100Devs … in one of these Twitter threads, pick three people in
your field that haven’t had a response and cheer them on.
8. Confidential & Proprietary
www.leadingedje.com
Confidential & Proprietary
www.leadingedje.com
Keep the Reader in the Test
Given
A function that returns a score.
Problem
The unit test has has some part of it abstracted in a way that makes reading the test
difficult.
9. Confidential & Proprietary
www.leadingedje.com
Confidential & Proprietary
www.leadingedje.com
Dare to Violate the DRY Principle
“Duplication of Code Logic”
Given
A function that adjusts a score.
Problem
The unit test has has some part of it abstracted in a way that makes reading the test
difficult.
10. Confidential & Proprietary
www.leadingedje.com
Confidential & Proprietary
www.leadingedje.com
Poisoning the Codebase: Non-Deterministic Factors
Given
A function that returns a category, the time of day.
Problem
The function under test is tightly coupled to a concrete data source, violating the
Single Responsibility Principle. The test cannot easily test the functionality.
11. Confidential & Proprietary
www.leadingedje.com
Confidential & Proprietary
www.leadingedje.com
Poisoning the Codebase: Side-Effects
Given
A function that updates the time of last motion and triggers something off or on.
Problem
The function under test has too many side effects that become hard to test.
13. Confidential & Proprietary
www.leadingedje.com
Confidential & Proprietary
www.leadingedje.com
Bad Test Double
“Testing the Mock”
Given
A service is used to get some data.
Problem
Replacing the service (test double) does not correctly take into account changes in
the service or returned data.
14. Confidential & Proprietary
www.leadingedje.com
Confidential & Proprietary
www.leadingedje.com
False Positives
Given
A function that processes some time delay.
Problem
The unit test does not get to the code inside the setTimeout and “does not fail” which
equates to a pass.
15. Confidential & Proprietary
www.leadingedje.com
Confidential & Proprietary
www.leadingedje.com
Excessive Setup
Given
A unit test that take a lot of setup code to run.
Problem
Excessive setup is more of a CODE SMELL than something where code can be shown
that is incorrect versus correct.
Just be aware that this is a case where care should be taken to examine why the
setup is so lengthy.
Solution
● If there is a legitimate need, increase documentation.
● If the code under test does not adhere to the Single Responsibility Principle,
maybe a refactor would be appropriate to make it more testable.
16. Confidential & Proprietary
www.leadingedje.com
Confidential & Proprietary
www.leadingedje.com
Testing Private Functionality Directly
Given
A class with private functionality that is difficult to test indirectly.
Problem
How do we test private functionality.
Solution
1. Indirectly
2. Make it public, directly.
3. Abstract it into a new service, directly.
17. Confidential & Proprietary
www.leadingedje.com
Confidential & Proprietary
www.leadingedje.com
Unit Recommendations
1. Unit Tests should be Readable.
2. One Assert per Test Method.
3. Avoid Test Interdependence.
4. Keep it Short, Sweet, and Visible.
5. Add them to the Build.
Good Unit Tests are a critical part of the development process and vital to the lifecycle of the code-under-test.
Previous employer …
No tests, there wasn’t enough time.
Flaky code.
Didn’t stand the test of time.
Another …
Script language story
180,000 lines of code with 97% Unit Test Coverage
Unit Tests in-depth.
Used TDD when bugs found before release.
3 Bug Cards in 365-days, two were missed requirement issues.
Others …
2,000+ Unit Tests against Promises (JavaScript) that passed, even when the tests should have failed.
No or limited Unit Testing because of monolithic code (3-6,000 line functions).
Let me reiterate,
Good Unit Tests are a critical part of the development process and vital to the lifecycle of the code-under-test.
Unit tests exercise very small parts of the application in complete isolation
They comparing the code-under-test’s actual behavior with the expected behavior.
The “complete isolation” part means that, when unit testing, you don’t typically connect your application with external dependencies such as databases, the filesystem, or HTTP services.
That allows unit tests to be fast and more stable since they won’t fail due to problems with those external services.
Each unit test is like a specification or example of how that part behaves in a specific scenario. By executing the suite of tests, developers can get immediate feedback when they change some codebase.
Testing code has been described as an "art form." It is, but it should not be.
There are many good patterns that should be followed when writing tests.
There are also many BAD patterns that developers should be aware of so that they can be avoided (or cleaned up).
Good production code achieves encapsulation.
It allows the reader to navigate large systems with ease, diving down into the details or rising to a higher level of abstraction, as needed.
Test code is a different beast.
A good unit test is often small enough that a developer can conceptualize all the logic at one time.
Adding layers of abstraction to test code increases its complexity.
Tests are a diagnostic tool, so they should be as simple and obvious as possible.
Well-Factored Code.
Code that is easy to read and reason about.
Code that can be tested easily.
Well Designed and often has a high level of complex.
Think of rulers. They have existed in the same form for hundreds of years.
They are uncomplicated and easy to interpret.
Suppose we invented a new ruler that measured in “abstract ruler units.”
To convert from “ruler units” to inches or centimeters, you would use a separate conversion chart.
If I offered such a ruler to a carpenter, they’d smack me in the face with it.
This layer of abstraction is unnecessary to a tool that gives clear, unambiguous information.
THE BROKEN CODE HERE IS NOT A BAD TEST
When you write a test, think about the next developer who will see the test break. They don’t want to read your entire test suite, and they certainly don’t want to read a whole inheritance tree of test utilities.
If a test breaks, the reader should be able to diagnose the problem by reading the test function in a straight line from top to bottom. If they have to jump out of the test to read ancillary code, the test has not done its job.
For strict adherents to the principle of DRY (“don’t repeat yourself”), the code we will see is horrifying.
We blatantly repeat ourselves; we copied lines from the previous test verbatim.
Worse, I’m arguing that my DRY-violating tests are better than tests that are free of repeated code. How can this be?
If you can achieve clear tests without duplicating code, that’s ideal, but remember that nonredundant code is the means, not the ends.
The end goal is clear, simple tests.
Before blindly applying DRY to your tests, think about what will make the problem obvious when a test fails.
Refactoring may reduce duplication, but it also increases complexity and potentially obscures information when things break.
In this case, tightly coupled data can make testing challenging.
Assumption that the code-under-test uses the current date to generate a string value.
The current date is a concrete data source.
The code gets the date AND generates a string based on the date.
Assume a class that turns light on or off at certain times OR with motion.
Tight coupling: calculated date and time.
Tied directly to an additional Switch class.
Often, functions have switch statements or if-branching and as they get larger the code is not refactored leaving too many paths through to test easily.
This is one that I see all the time.
A mock service is created that allows functionality to be “mocked.”
In reality, we are testing the mock in this case.
A better pattern is to create a mock service FROM the original service being injected.
Then add mock data or spy on functionality from within the service.
These occur when …
Forgetting to EXPECT (some test suites warn of this).
When testing ASYNCHRONOUS activity. Promises that DON’T FAIL.
Jasmine used to use DONE …
DONE has become unreliable in certain scenarios (see the commented out code).
JavaScript allows for async/await and try/catch. These are better patterns and more readable, as well.
This is a challenging one to show examples of …
There is code.
Let’s simply talk about the issues.
This is more of a code smell … that something can be wrong in the test setup or in the code-under-test.
Code-under-test should be examined to see if a refactor can help.
There can be legitimate needs for larger setups.
There is a challenging one to show a FIXED version of …
There is code.
Private code can be tested via exposed functionality.
Private code can be made public (JavaScript: everything is accessible at some level).
Private code can be abstracted into a service …
The code has to be public for injection.
The code can be tested directly since it is public.
I get asked about the first one often.
Even in my tests, I assert more than once.
I recommend checking to see if the functionality can be refactored to allow for ONE TEST per METHOD.
Test Order shouldn’t matter.
Unit Tests absolutely should be a part of the pipeline.
<!-- HTML Credit Code for Can Stock Photo -->
<a href="https://www.canstockphoto.com">(c) Can Stock Photo / catalby</a>