Outline
This is from a tweet which you can find here
Previous Posts:
- Mock Types,
- The AAA Structure
- Interaction vs State Testing
- DAMP vs DRY
- Thinking from the Client's Perspective
A good test should:
- be easy to write
- be quick to refactor
- have a useful name
- not break when you refactor
- be deterministic
- be isolated
- Execute quickly
- Test only 1 thing
- Be easy to throw away
- Be easy to read
1. Be easy to write.
If you find yourself struggling to write a unit test, this is a good indication that there's a problem. Use this like a code smell. An indication that something is amiss. Honestly the most likely problem is your code.
But you may also be trying to either over-architect your test, or you may be testing too much, or you may be testing something less valuable. Use this as an indication to reconsider your approach, or seek help from others.
2. Be Quick to Refactor
We refactor tests for 2 reasons:
- to make the test better/easier to read.
- to reflect a refactoring in the SUT (code being tested)
A good test is easily refactored. If you want to change your test, this should be easy.
If the SUT changes, say you add another parameter, or refactor the internal algorithm, updating your test to reflect this change should be straightforward. If this isn't true, this is a good indication that something is amiss...either your code or the test.
Ideally it would take you 1 minute or less to refactor a unit test if the SUT changes. This is only an idea. But if it takes you 20 minutes to refactor 1 test, something is almost definitely amiss.
3. Have a useful name.
it('should work')
is NOT useful. If you see this, fix it immediately.
it('should require a paragraph to describe')
this is ALSO not useful.
Here's some good name examples:
'should return false if the password is incorrect'
'should remove all items from the collection when clear is called'
'should include the state taxes in the total if the state has a tax rate'
4. Not break when you refactor
This one is difficult. The more you write interaction tests, the more difficult this is - see part 3 of this series for info on interaction vs state-based testing. https://twitter.com/josepheames/status/1293945128260796418
Let's say in an addItemToCart method you first add an item to the local cart, and then you fire off a call to update the server. If you later on change to fire off the call before you add the item to the local cart, your tests probably shouldn't break.
Good tests care little to nothing about the internal implementation of an algorithm. Again, this one can be really challenging to achieve.
5. Be deterministic
If your code is based on the clock, a random number generator (same thing as the clock) or anything else nondeterministic, you should mock that aspect of the code and make the test deterministic.
Not to mention that a tests should never occasionally fail because of something like a race condition
6. be isolated
A unit tests should be COMPLETELY independent from all other unit tests. No matter what order you execute your unit tests, or which tests are executed, this should not affect any tests. They key to this is having zero shared state between tests.
7. Execute quickly
If your unit tests take too long to run, you won't run them. An ideal is 5 seconds to run the relevant portion of your unit test suite. 10 or even 15 seconds is ok. The entire suite should ideally run in 5 minutes or less.
8. Test only 1 thing.
A good test doesn't test multiple states, or multiple state transitions. Don't assert that the cartis empty, then add an item and then another assert that the cart now has 1 item. That's 2 things. Test only 1 thing.
This is the Single Responsibility Principle of unit tests. Another way to put this is that a test should only have 1 reason to fail.
9. Be easy to throw away.
Far too often people complain that their tests hold them back from refactoring or re-architecting their code. If your test is easy to throw away, then it can't hold you back.
If you change your code and now don't need class A, then you should have no problem deleting all the unit tests for class A. If this isn't true, something is probably amiss.
10. Be easy to read
A good test is fairly short, and is readable. Tests are a form of documentation of our code. If its impossible for someone to understand what a test is doing and why, the test's value is significantly reduced.
A useful name (#3) is an important part of this, but the code is also important.
If your tests aren't easy to read, then it's a good indication that something is amiss...either your test or your code.
Summary:
After looking through these poitns, you'll see that unit tests are a kind of barometer or canary for your code. if the tests are a problem, the code likely is.
Your unit tests can point out problems in your code. Don't ignore them.
Discounted Courses
Here at Thinkster, we use educational science to teach you five times faster than you would learn the same topics elsewhere. By joining as a Pro subscriber you have access to our complete library on courses on Angular, React, Vue, JavaScript, and More.
To help you get started, use this link for a discount on a monthly membership.
Or save even more with an annual membership.