|
| 1 | +# Properties of a good test |
| 2 | +As mentioned previously, simply writing a test is not the same as writing a good |
| 3 | +test. In this section, we'll examine some properties of what makes a good test. |
| 4 | + |
| 5 | +## Size: Focused tests |
| 6 | +A well-written test should not test too many things at once. In general, test |
| 7 | +cases should be well-isolated and have a [single |
| 8 | +responsibility](https://en.wikipedia.org/wiki/Single-responsibility_principle) |
| 9 | +or intention. |
| 10 | + |
| 11 | +Consider the previous example of the `absolute()` function. Suppose the test |
| 12 | +instead looked like this: |
| 13 | + |
| 14 | +```javascript |
| 15 | +describe('absolute', () => { |
| 16 | + it('should correctly handle inputs', () => { |
| 17 | + const result1 = absolute(1); |
| 18 | + expect(result0).toBe(1); |
| 19 | + const result1 = absolute(-1); |
| 20 | + expect(result1).toBe(1); |
| 21 | + const result2 = absolute(0); |
| 22 | + expect(result2).toBe(0); |
| 23 | + }); |
| 24 | +}); |
| 25 | +``` |
| 26 | + |
| 27 | +Now compare this to the original. Which one do you find more readable and |
| 28 | +organized? While this is a simple example, separating the cases for clarity |
| 29 | +becomes especially important with more complex functions. |
| 30 | + |
| 31 | +```javascript |
| 32 | +describe('absolute', () => { |
| 33 | + it('should return positive number if input positive', () => { |
| 34 | + const result = absolute(1); |
| 35 | + expect(result).toBe(1); |
| 36 | + }); |
| 37 | + it('should return positive number if input negative', () => { |
| 38 | + const result = absolute(-1); |
| 39 | + expect(result).toBe(1); |
| 40 | + }); |
| 41 | + it('should return zero if input is zero', () => { |
| 42 | + const result = absolute(0); |
| 43 | + expect(result).toBe(0); |
| 44 | + }); |
| 45 | +}); |
| 46 | +``` |
| 47 | + |
| 48 | +## Redundancy: avoid overlapping test cases |
| 49 | +As a related topic, you usually want to avoid test cases that overlap too much |
| 50 | +with existing test cases. For example, consider the following test case for the |
| 51 | +above. Is it useful, or is it redundant, overlapping with one of the test cases |
| 52 | +above? |
| 53 | + |
| 54 | +```javascript |
| 55 | +it('should return positive number if input is large and negative', () => { |
| 56 | + const result = absolute(-100); |
| 57 | + expect(result).toBe(100); |
| 58 | +}); |
| 59 | +``` |
| 60 | + |
| 61 | +This case is redundant. Testing `absolute(-1)` and `absolute(-100)` is not very |
| 62 | +useful; the two test cases overlap. We want to avoid this. |
| 63 | + |
| 64 | +It is often asked how much overlap is okay. Inevitably, some test cases will |
| 65 | +overlap with each other. There is no correct answer here: just know that some |
| 66 | +overlap is okay, but you want to do your best to avoid redundancy by selecting |
| 67 | +test cases that are representative of the cases of your function. |
| 68 | + |
| 69 | +In the |
| 70 | +programmer's mindset, usually there are a set of possible scenarios to consider. |
| 71 | +For example, in the case of the `absolute()` function, there are three distinct |
| 72 | +scenarios that define the behavior: positive, negative, and zero. Whether the |
| 73 | +negative number is large or not makes no difference. |
| 74 | + |
| 75 | +## Thoroughness |
| 76 | +In this module, it was mentioned several times that good test coverage means |
| 77 | +that, if the code is behaving incorrectly, even in one line or in one |
| 78 | +if-condition, some line will fail. |
| 79 | + |
| 80 | +A common problem with tests is that some case is missing. Perhaps when certain |
| 81 | +arguments are passed to your function, it behaves differently. A good suite of |
| 82 | +tests will make sure to cover these cases. |
| 83 | + |
| 84 | +## Organization: Arrange-Act-Assert |
| 85 | +Tests generally follow approximately the same pattern, and this has been |
| 86 | +crystallized in a commonly adopted idea called ["Arrange Act |
| 87 | +Assert."](https://automationpanda.com/2020/07/07/arrange-act-assert-a-pattern-for-writing-good-tests/) |
| 88 | + |
| 89 | +1. **Arrange**: Many tests require some type of setup; the setup should come |
| 90 | + initially. |
| 91 | +2. **Act**: In the act step, the actual behavior being tested should be invoked, |
| 92 | + such as a function call, an API call, a component render, etc. |
| 93 | +3. **Assert**: Finally, to ensure correctness, assert the expected outcomes. |
| 94 | + This was seen earlier using the `expect` function. Expect and assert, in this |
| 95 | + context, are synonymous. |
| 96 | + |
| 97 | +## Additional readings |
| 98 | +This [StackOverflow |
| 99 | +question](https://stackoverflow.com/questions/61400/what-makes-a-good-unit-test) |
| 100 | +gives some interesting insights into what are considered good properties of a |
| 101 | +unit test. |
| 102 | + |
| 103 | +Google has a good resource called *Testing on the Toilet*. On the back of the |
| 104 | +door in every toilet stall at Google, you can find a one-page flyer that |
| 105 | +describes some aspect of testing (that's how important testing is!). These are |
| 106 | +publicized. Some good ones for junior developers are included below: |
| 107 | + |
| 108 | +- [Keep tests focused](https://testing.googleblog.com/2018/06/testing-on-toilet-keep-tests-focused.html) |
| 109 | +- [DAMP](https://www.googblogs.com/testing-on-the-toilet-tests-too-dry-make-them-damp/). Don't overuse functions in test code, focus on readability. |
| 110 | +- [Just say no to end-to-end tests](https://testing.googleblog.com/2015/04/just-say-no-to-more-end-to-end-tests.html). Avoid overusing tests that connect too many parts of your system. |
| 111 | + |
0 commit comments