# About Unittest Unit Testing is the process of writing and automatically running tests to ensure that the functions you code work as expected. ## What Is Unit Testing and Why Is It Important? ### What is unittest? A "Unit" is the smallest possible software component in your app (i.e, functions, classes, or components). Individual unit tests make sure that the core component of your app is behaving as expected, and that a future commit to one area of your code doesn't break code in another. If it does, you likely have a bug in either your new or old code (or in a poorly written/outdated test). The goal of unit tests is obvious---reduce bugs, especially bugs that arise from integration. A developer might think everything is fine locally and commit their code, only to find out that another commit has broken the app. Unit testing helps catch some of these defects before they become issues, and when combined with automated continuous integration pipelines, can make sure that the daily build is always working properly. Unit testing isn't limited to small bits of code; You can also unit test larger components that make use of multiple other functions, that may themselves be unit tested. This helps you track down errors more effectively---is the error in the methods of the large component object, or in one of the other components it makes use of? While unit tests are important, they also aren't the only testing you should be doing. Running End-to-End UI testing and manual human review will catch plenty of logic bugs that unit tests may miss when every unit is operating as expected. ### Unit Testing Leads to Cleaner Codebases One of the main problems with legacy codebases is dinosaur code---code so old that it's basically a black box, you might have no idea how it works, but somehow it does work, and you don't want to refactor it due to fears it might break everything. In a way, when you write unit tests, you're writing documentation for it. You might not have to write a whole manual, but you'll always be defining two things: what to give the function, and what it returns, similarly to how you'd define an API schema. With these two bits of information, it's clear what the function does, and how to integrate it into your app. Obviously, unit testing doesn't solve existing legacy codebases, but it does prevent you from writing this type of dinosaur code in the first place. Often, you'll be able to write your tests before the actual function you're testing. If you know what your function needs to do, writing the test first forces you to think about the end result of your code, and what it is responsible for. If you like this effect of unit testing, you might also be interested in TypeScript---a compiled superset of JavaScript that makes it strongly typed. You'll still want to write unit tests, but knowing what types a function gives and takes while you're coding is a very useful feature. ## How to write an unittest? ### A sample example Now we will see how to use the Python unittest framework to write the three tests executed in the previous section. Firstly, let’s assume that the main application code is in the file **user.py**. You will write your unit tests in a file called **test_user.py**. **The common naming convention for unit tests: the name of the file used for unit tests simply prepends “test_” to the .py file where the Python code to be tested is.** To use the unittest framework we have to do the following: - import the **unittest module** - create a test class that inherits **unittest.TestCase**. We will call it TestUser. - add one method for each test. - add an **entry point** to execute the tests from the command line using **unittest.main**. Here is a Python unittest example: ```python import unittest class TestUser(unittest.TestCase): def test_user_activation(self): pass def test_user_points_update(self): pass def test_user_level_change(self): pass if __name__ == '__main__': unittest.main() ``` You have created the structure of the test class. Before adding the implementation to each unit test class method let’s try to execute the tests to see what happens. To run unit tests in Python you can use the following syntax: ```bash python test_user.py # ... # --------------------------------------------------------------------------- # Ran 3 tests in 0.000s # # OK ``` ### How to write? Now that we have the structure of our test class we can implement each test method. Unit tests have this name because they test units of your Python code, in this case, the behavior of the methods in the class User. **Each unit test should be designed to verify that the behavior of our class is correct when a specific sequence of events occurs. As part of each unit test, you provide a set of inputs and then verify the output is the same as you expected using the concept of assertions.** In other words, each unit test automates the manual tests we have executed previously. Technically, you could use the [assert statement](https://docs.python.org/3/library/unittest.html#unittest.TestCase.assertEqual) to verify the value returned by methods of our User class. In practice, the unittest framework provides its assertion methods. We will use the following in our tests: - assertEqual - assertTrue Let’s start with the first test case… …actually before doing that we need to be able to see the User class from our test class. How can we do that? This is the content of the current directory: ```bash $ ls test_user.py user.py ``` To use the User class in our tests add the following import after the unittest import in *test_user.py*: ```python from user import User ``` And now let’s implement three unit tests. ***1st Use Case: User state is active after activation has been completed*** ```python def test_user_activation(self): user1 = User() user1.activate() self.assertTrue(user1.is_active()) ``` In this test, we activate the user and then assert that the *is_active()* method returns True. ***2nd Use Case: User points are incremented correctly*** ```python def test_user_points_update(self): user1 = User() user1.add_points(25) self.assertEqual(user1.get_points(), 25) ``` This time instead of using **assertTrue** we have used **assertEqual** to verify the number of points assigned to the user. ***3rd Use Case: User level changes from 1 to 2 when the number of points is greater than 200*** ```python def test_user_level_change(self): user1 = User() user1.add_points(205) self.assertEqual(user1.get_level(), 2) ``` The implementation of this unit test is similar to the previous one with the only difference being that we are asserting the value of the level for the user. And now it’s the moment to run our tests… ```bash $ python test_user.py ... --------------------------------------------------------------------------- Ran 3 tests in 0.000s OK All the tests are successful! ``` ### An Example of Unit Test Failure Before completing this tutorial I want to show you what would happen if one of the tests fails. First of all, let’s assume that there is a typo in the *is_active()* method: ```python def is_active(self): return self.profile['active_user'] ``` I have replaced the attribute *active* of the user profile with *active_user* which doesn’t exist in the profile dictionary. Now, run the tests again… ```shell $ python test_user.py E.. =========================================================================== ERROR: test_user_activation (__main__.TestUser) --------------------------------------------------------------------------- Traceback (most recent call last): File "test_user.py", line 9, in test_user_activation self.assertTrue(user1.is_active()) File "/opt/Python/Tutorials/user.py", line 9, in is_active return self.profile['active_user'] KeyError: 'active_user' --------------------------------------------------------------------------- Ran 3 tests in 0.000s FAILED (errors=1) ``` In the first line of the test execution you can see: ```shell E.. ``` Each character represents the execution of a test. **E** indicates an error while a **dot** indicates a success. This means that the first test in the test class has failed and the other two are successful. The output of the test runner also tells us that the error is caused by the assertTrue part of the test_user_activation method. This helps you identify what’s wrong with the code and fix it. To further learn how to write unittest, one can refer to our **tests** directory or go to the python website for more information. ## How to run unittest? Recently we have code covered about 80% by unittest which including most of the single units and the whole run loop. To run all unittests, one need to set several environment variables to make `test_zrunnner.py` (for testing the whole loop) work. For example, one should use command as the following: ```bash CALYPSO_M3GNET_PYTHON="/home/wangzy/soft/anaconda3/envs/m3gnet/bin/python" CALYPSO_CHGNET_PYTHON="/home/wangzy/soft/anaconda3/envs/chgnet/bin/python" make test ``` In this command, `CALYPSO_M3GNET_PYTHON` and `CALYPSO_CHGNET_PYTHON` are used to set the python path of m3gnet and chgnet for running the `threadrunner.py` and `splitrunner.py`. If one do not want to test the runner, typing `make test` is sufficient. Behind this command is ```Makefile .PHONY: all init test lint format clean init: git config --unset-all core.hooksPath # git config core.hooksPath .git/hooks/ cp .githooks/* .git/hooks/ pre-commit install test: python -m coverage run -m unittest -v python -m coverage xml python -m coverage report @make lint lint: python -m mypy calypso tests python -m black --check -S calypso tests python -m isort --check calypso tests format: python -m black -S calypso tests docs python -m isort calypso tests docs manual: clean cd docs; make all-html; cd .. clean: rm -rf ./docs/developer_source/api rm -rf docs/build* ``` ## How to set an unittest workflow in GitHub action? pass