# Clean Code - 9 Unit Tests Writing automated unit tests is encouraged because of the trend of Agile and TDD movements. However, it is important to **write good tests instead of just writing tests**. ## The Three Laws of TDD Other resources: https://blog.cleancoder.com/uncle-bob/2014/12/17/TheCyclesOfTDD.html https://www.ibm.com/garage/method/practices/code/practice_test_driven_development/ > The tests and the production code are written together, with the tests just a few seconds ahead of the production code. > > > ### First Law > You may not write production code until you have written a failing unit test. > > ### Second Law > You may not write more of a unit test than is sufficient to fail, and not compiling is failing. > > ### Third Law > You may not write more production code than is sufficient to pass the currently failing test. #### Example **Phase 1** Write a first failing unit test: ``` // production codes // first method function getNumbersFromStringKey(sourceStr: string) : number { } // below are unit tests function testGetNumbersFromStringKeyWithNumberStr() : void { const result = getNumbersFromStringKey('010789666'); expect(!Number.isNaN(result)).toEqual(true); } ``` **Phase 2** Since the unit test `testGetNumbersFromStringKeyWithSpaces` will fail, should modify the production codes to pass the test case, instead of creating more test cases: ``` // production codes // first method function getNumbersFromStringKey(sourceStr: string) : number { return +sourceStr; } // below are unit tests function testGetNumbersFromStringKeyWithNumberStr() : void { const result = getNumbersFromStringKey('010789666'); expect( !Number.isNaN(result) ).toEqual(true); } function testGetNumbersFromStringKeyWithSpaces() : void { const result = getNumbersFromStringKey('010 789 666'); expect( !Number.isNaN(result) ).toEqual(true); } ``` **Phase 3** Can start to write another production method after all unit tests pass: ``` // production codes // first method function getNumbersFromStringKey(sourceStr: string) : number { const result = +sourceStr.replace(/\D/g, ""); return result; } // below are unit tests function testGetNumbersFromStringKeyWithNumberStr() : void { const result = getNumbersFromStringKey('010789666'); expect( !Number.isNaN(result) ).toEqual(true); } function testGetNumbersFromStringKeyWithSpaces() : void { const result = getNumbersFromStringKey('010 789 666'); expect( !Number.isNaN(result) ).toEqual(true); } function testGetNumbersFromStringKeyWithNotNumber() : void { const result = getNumbersFromStringKey('010*789++666'); expect( !Number.isNaN(result) ).toEqual(true); } ``` ## Keeping Tests Clean When tests are dirty, it's almost the same as no tests. Discipline is: > Test code is just as important as production code. #### How dirty tests affect production codes - Scenario 1: When updating the function body of the production codes (still the same API, only change the body of a function) - The dirty tests will not fail since the goal of functions does not change - Scenario 2: When updating the entire API (for example, new functions take more parameters, or some functions no longer exist) - The dirty tests will fail and it takes lots of effort to fix them to pass the new functions ![](https://hackmd.io/_uploads/B1ggXGmLj.png) ### Tests Enable the -ilities Keeping unit tests clean helps keep production code flexible and makes changing it easier and more confident. ## Clean Tests The clean test is all about readability. Readability includes: - Clarity - Simplicity - Density of expression: eg, if, while, should not have too many, should not be too complicated #### Bad unit tests example in Jave ![](https://hackmd.io/_uploads/SJ_Duf7Is.png) #### Same unit test after refactor Use the BUILD-OPERATE-CHECK pattern http://fitnesse.org/FitNesse.FullReferenceGuide.UserGuide.WritingAcceptanceTests.AcceptanceTestPatterns.BuildOperateCheck ![](https://hackmd.io/_uploads/SknzsMm8s.png) ### Domain-Specific Testing Language Write your own API for test use, and you need to constantly update and refactor according to test needs. #### Example **Before having APIs:** ![](https://hackmd.io/_uploads/rkj7bXm8i.png) **After writing and using the APIs:** ![](https://hackmd.io/_uploads/SJNw-mmUo.png) ### A Dual Standard While APIs for testing needs to be as simple and clean as production code, they **don't need to be as efficient as production code**. The reason is that the tests are run in a test environment, which has different requirements than the production environment. #### Example **Need to move eyes back and forth to read the test:** ![](https://hackmd.io/_uploads/By6p7XXLo.png) **Increase the readability:** ![](https://hackmd.io/_uploads/ryEt4XXIo.png) **How the `getState` method is implemented:** ![](https://hackmd.io/_uploads/H1dqrQ7Is.png) ## One Assert per Test Sometimes the **same code can appear in multiple tests** when there is only one assert in each test: ![](https://hackmd.io/_uploads/ByMaIm7Ui.png) #### Template Method To solve the above duplication of code problem, can use the Template Method. Source https://www.artima.com/weblogs/viewpost.jsp?thread=35578 Use **Given, When, Then**: - Given - preconditions, things should be done **before tests**, and be written as a function - When - actions - Then - verification Example in pseudocodes: ``` Scenario: test sign-in user checkout Given: create an account and sign in When: put a product in the cart and checkout Then: the order summary has the user and product information ``` ### Single Concept per Test Test a single concept in each test function. When there are multiple concepts in a test: ![](https://hackmd.io/_uploads/ByDn4yALj.png) **The scenarios and preconditions in the above test:** > Given the last day of a month with 31 days (like May): **Scenario 1:** Add 1 month, the last day of the new month should be 30th **Scenario 2:** Add 2 months, the last day of the new month should be 31st > Given the last day of a month with 30 days in it (like June): **Scenario 3:** Add 1 month, the last day of the new month should be 30th The above test should be divided into 3 tests. Should also include other missing tests like **the last day of a month with 28 days in it (like February)** ## F.I.R.S.T ### Fast > Tests should be fast. They should run quickly. ### Independent > Tests should not depend on each other. ### Repeatable > Tests should be repeatable in any environment. For example, tests should be able to run on both the production environment and the OA environment. ### Self-Validating > The tests should have a **boolean output**. Either they pass or fail. ### Timely > The tests need to be written in a timely fashion. Unit tests should be written just **before** the production code that makes them pass. ## Conclusion We should always keep our tests clean. ###### tags: `learn` `clean code` `test automation`