Unit Testing, How and Why?
Unit Testing is needed to validate that each unit of the software performs as designed. Interesting , isn’t it?
The whole point of unit testing (in any language) is to allow you to be confident that that each of the ‘components’ that make up your software application work as expected. By component that could be a function, or a method, or a class. by testing each component and showing that they do what you expect, then you are in a good position to get a working application when you glue all the components together. Ideally unit tests should be written at the same time as the software being tested — if not before.
There are a number of packages which aid unit testing on Python: unittest (part of the standard library), doctest (again in the standard library), pytest , nose and hypothesis.
Of these choose whichever meets your personal style, with one caveat — doctest is a good idea only if you have functions/methods with a few simple test cases and very little setup; in doctest you write your unit tests as part of your documentation string for your function/method, and you might imagine the issues if you have complex unit test cases how long your doc-string might become.
- Personally I use the unittest library: I like the ability to group test cases into different classes and have both per class and per test case setup. I also like the very rich comparison features in unittest library including comparing lists entry by entry and also comparing lists by content (regardless of order) for example. Unittest also has features to confirm whether exceptions are raised (or not), and whether log file messages are raised (or not).
- I have not used pytest or nose — but I have heard good things about both of them too.
- Hypothesis is a very clever library — where you tend not to specify the actual test case; what you do is specify the types of the arguments, and the ‘invariants’, i.e. what must be true for this function/method to work. The Hypothesis library then chooses a large set of random input values in order to prove that the invariant holds in all cases. You can also specify directly some values that must be tested. I have not used Hypothesis apart from some very simple examples.
Whichever framework you use — there are a few best practice things to remember :
- Consider edge cases — do you functions accept None (as a user might expect them to, what about empty strings, or very large or small numeric values
- Consider what the user of your code will see — your test cases should test what the user will see, and not how you think you have written the code — as an example :
- You write a function that takes two file names: it opens the first, reads the contents, filters that file in some way and then write the data to the second file. When the function completes both files are closed.
- You know you wrote the function so that it always closes both files, but your unittest cases should still test that is the case.
- Closing the files is part of the user expectation of your function, and you should always write your test cases to test that the function meets the user expectations.
- Isolate the code as far as possible from other systems. For instance — your code under test opens a file, but to test it that means you have to create a file — write it to disk, and remember to delete it afterwards. Maybe, therefore, you could use the mock library to mimic a file being available so that even if your test fails there are never test files cluttering your disk drive. Another option might be to use a Fake file system — such as : pyfakefs which is essentially a file system built on top of mock library. pyfakefs makes it very easy to create whole directory structures, files etc — and change permissions, dates and other features of the files; safe in the knowledge that your test case never need write any test data to any disk — it is all done in memory.
You can use mock to pretend to be almost anything — if the code being tested imports a library — you can use mock to build a fake version of it and control all of the inputs and outputs (and exceptions). - Build more test cases than you think you need — you really can never do too much testing.
- Look at tools such as coverage to measure how effective your tests cases are at testing all paths through the code. Coverage measurements aren’t the whole answer — but they are a useful metric nonetheless.
- Tools such as tox might be useful to give you the ability to run your test cases across multiple environments in a single command.
- If you use GitHub you can link your repository to systems such as travis to get automated testing of your released code. Travis can be configured to test against Linux, Windows and Mac as far as I know.
Linkedin : https://www.linkedin.com/in/ashutosh-kumar-9a109864/