link experiment
This commit is contained in:
264
Notes/08_Testing_debugging/01_Testing.md
Normal file
264
Notes/08_Testing_debugging/01_Testing.md
Normal file
@@ -0,0 +1,264 @@
|
||||
# 8.1 Testing
|
||||
|
||||
## Testing Rocks, Debugging Sucks
|
||||
|
||||
The dynamic nature of Python makes testing critically important to most applications.
|
||||
There is no compiler to find your bugs. The only way to find bugs is to run the code and make sure you try out all of its features.
|
||||
|
||||
## Assertions
|
||||
|
||||
The assertion statement is an internal check for the program.
|
||||
If an expression is not true, it raises a `AssertionError` exception.
|
||||
|
||||
`assert` statement syntax.
|
||||
|
||||
```python
|
||||
assert <expression> [, 'Diagnostic message']
|
||||
```
|
||||
|
||||
For example.
|
||||
|
||||
```python
|
||||
assert isinstance(10, int), 'Expected int'
|
||||
```
|
||||
|
||||
It shouldn't be used to check the user-input.
|
||||
|
||||
### Contract Programming
|
||||
|
||||
Also known as Design By Contract, liberal use of assertions is an approach for designing
|
||||
software. It prescribes that software designers should define precise
|
||||
interface specifications for the components of the software.
|
||||
|
||||
For example, you might put assertions on all inputs and outputs.
|
||||
|
||||
```python
|
||||
def add(x, y):
|
||||
assert isinstance(x, int), 'Expected int'
|
||||
assert isinstance(y, int), 'Expected int'
|
||||
return x + y
|
||||
```
|
||||
|
||||
Checking inputs will immediately catch callers who aren't using appropriate arguments.
|
||||
|
||||
```python
|
||||
>>> add(2, 3)
|
||||
5
|
||||
>>> add('2', '3')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
AssertionError: Expected int
|
||||
>>>
|
||||
```
|
||||
|
||||
### Inline Tests
|
||||
|
||||
Assertions can also be used for simple tests.
|
||||
|
||||
```python
|
||||
def add(x, y):
|
||||
return x + y
|
||||
|
||||
assert add(2,2) == 4
|
||||
```
|
||||
|
||||
This way you are including the test in the same module as your code.
|
||||
|
||||
*Benefit: If the code is obviously broken, attempts to import the module will crash.*
|
||||
|
||||
This is not recommended for exhaustive testing.
|
||||
|
||||
### `unittest` Module
|
||||
|
||||
Suppose you have some code.
|
||||
|
||||
```python
|
||||
# simple.py
|
||||
|
||||
def add(x, y):
|
||||
return x + y
|
||||
```
|
||||
|
||||
You can create a separate testing file. For example:
|
||||
|
||||
```python
|
||||
# testsimple.py
|
||||
|
||||
import simple
|
||||
import unittest
|
||||
```
|
||||
|
||||
Then define a testing class.
|
||||
|
||||
```python
|
||||
# testsimple.py
|
||||
|
||||
import simple
|
||||
import unittest
|
||||
|
||||
# Notice that it inherits from unittest.TestCase
|
||||
class TestAdd(unittest.TestCase):
|
||||
...
|
||||
```
|
||||
|
||||
The testing class must inherit from `unittest.TestCase`.
|
||||
|
||||
In the testing class, you define the testing methods.
|
||||
|
||||
```python
|
||||
# testsimple.py
|
||||
|
||||
import simple
|
||||
import unittest
|
||||
|
||||
# Notice that it inherits from unittest.TestCase
|
||||
class TestAdd(unittest.TestCase):
|
||||
def test_simple(self):
|
||||
# Test with simple integer arguments
|
||||
r = simple.add(2, 2)
|
||||
self.assertEqual(r, 5)
|
||||
def test_str(self):
|
||||
# Test with strings
|
||||
r = simple.add('hello', 'world')
|
||||
self.assertEqual(r, 'helloworld')
|
||||
```
|
||||
|
||||
*Important: Each method must start with `test`.
|
||||
|
||||
### Using `unittest`
|
||||
|
||||
There are several built in assertions that come with `unittest`. Each of them asserts a different thing.
|
||||
|
||||
```python
|
||||
# Assert that expr is True
|
||||
self.assertTrue(expr)
|
||||
|
||||
# Assert that x == y
|
||||
self.assertEqual(x,y)
|
||||
|
||||
# Assert that x != y
|
||||
self.assertNotEqual(x,y)
|
||||
|
||||
# Assert that x is near y
|
||||
self.assertAlmostEqual(x,y,places)
|
||||
|
||||
# Assert that callable(arg1,arg2,...) raises exc
|
||||
self.assertRaises(exc, callable, arg1, arg2, ...)
|
||||
```
|
||||
|
||||
This is not an exhaustive list. There are other assertions in the module.
|
||||
|
||||
### Running `unittest`
|
||||
|
||||
To run the tests, turn the code into a script.
|
||||
|
||||
```python
|
||||
# testsimple.py
|
||||
|
||||
...
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
```
|
||||
|
||||
Then run Python on the test file.
|
||||
|
||||
```bash
|
||||
bash % python3 testsimple.py
|
||||
F.
|
||||
========================================================
|
||||
FAIL: test_simple (__main__.TestAdd)
|
||||
--------------------------------------------------------
|
||||
Traceback (most recent call last):
|
||||
File "testsimple.py", line 8, in test_simple
|
||||
self.assertEqual(r, 5)
|
||||
AssertionError: 4 != 5
|
||||
--------------------------------------------------------
|
||||
Ran 2 tests in 0.000s
|
||||
FAILED (failures=1)
|
||||
```
|
||||
|
||||
### Commentary
|
||||
|
||||
Effective unit testing is an art and it can grow to be quite complicated for large applications.
|
||||
|
||||
The `unittest` module has a huge number of options related to test
|
||||
runners, collection of results and other aspects of testing. Consult
|
||||
the documentation for details.
|
||||
|
||||
### Third Party Test Tools
|
||||
|
||||
We won't cover any third party test tools in this course.
|
||||
|
||||
However, there are a few popular alternatives and complements to
|
||||
`unittest`.
|
||||
|
||||
* [pytest](https://pytest.org) - A popular alternative.
|
||||
* [coverage](http://coverage.readthedocs.io) - Code coverage.
|
||||
|
||||
## Exercises
|
||||
|
||||
In this exercise, you will explore the basic mechanics of using
|
||||
Python's `unittest` module.
|
||||
|
||||
In earlier exercises, you wrote a file `stock.py` that contained a `Stock`
|
||||
class. For this exercise, it assumed that you're using the code written
|
||||
for Exercise 7.3. If, for some reason, that's not working,
|
||||
you might want to copy the solution from `Solutions/7_3` to your working
|
||||
directory.
|
||||
|
||||
### (a) Writing Unit Tests
|
||||
|
||||
In a separate file `test_stock.py`, write a set a unit tests
|
||||
for the `Stock` class. To get you started, here is a small
|
||||
fragment of code that tests instance creation:
|
||||
|
||||
|
||||
```python
|
||||
# test_stock.py
|
||||
|
||||
import unittest
|
||||
import stock
|
||||
|
||||
class TestStock(unittest.TestCase):
|
||||
def test_create(self):
|
||||
s = stock.Stock('GOOG', 100, 490.1)
|
||||
self.assertEqual(s.name, 'GOOG')
|
||||
self.assertEqual(s.shares, 100)
|
||||
self.assertEqual(s.price, 490.1)
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
```
|
||||
|
||||
Run your unit tests. You should get some output that looks like this:
|
||||
|
||||
```
|
||||
.
|
||||
----------------------------------------------------------------------
|
||||
Ran 1 tests in 0.000s
|
||||
|
||||
OK
|
||||
```
|
||||
|
||||
Once you're satisifed that it works, write additional unit tests that
|
||||
check for the following:
|
||||
|
||||
- Make sure the `s.cost` property returns the correct value (49010.0)
|
||||
- Make sure the `s.sell()` method works correctly. It should
|
||||
decrement the value of `s.shares` accordingly.
|
||||
- Make sure that the `s.shares` attribute can't be set to a non-integer value.
|
||||
|
||||
For the last part, you're going to need to check that an exception is raised.
|
||||
An easy way to do that is with code like this:
|
||||
|
||||
```python
|
||||
class TestStock(unittest.TestCase):
|
||||
...
|
||||
def test_bad_shares(self):
|
||||
s = stock.Stock('GOOG', 100, 490.1)
|
||||
with self.assertRaises(TypeError):
|
||||
s.shares = '100'
|
||||
```
|
||||
|
||||
[Next](02_Logging)
|
||||
Reference in New Issue
Block a user