-
-
Notifications
You must be signed in to change notification settings - Fork 42
introduce unittest #11
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: gh-pages
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
--- | ||
layout: page | ||
title: Testing | ||
subtitle: The Unittest Module | ||
minutes: 10 | ||
--- | ||
> ## Learning Objectives {.objectives} | ||
> | ||
> - Understand how to use Unittest to write and run test cases | ||
> - Understand that Unittest can be used for many styles of test, not just unit tests | ||
> - | ||
> - | ||
|
||
The Python standard library provides a module for writing and running tests, called `unittest`, which is documented for [Python3.6](https://docs.python.org/3.6/library/unittest.html?highlight=unittest#module-unittest) and [Python2.7](https://docs.python.org/2/library/unittest.html?highlight=unittest#module-unittest). | ||
|
||
This module is useful for writing all styles of test, including unit tests and functional tests. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since we already cover pytest, perhaps this would be a good place to justify the need to learn both (or when it is or is not necessary to learn both). My general feeling is that pytest is much more friendly to beginners. |
||
|
||
## Functional Tests | ||
|
||
Functional testing is a way of checking software to ensure that it has all the required functionality that's specified within its functional requirements. This will test many methods and may interact with dependencies like file access. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. line length. |
||
|
||
Functional tests are often the first tests that are written as part of writing a program. They can link closely to a requirement: "This programme shall ...". | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. programme -> program |
||
|
||
These tests are testing overall behaviour. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. behavior |
||
|
||
They are especially useful if code is being restructured or re-factored, to build confidence that the key behaviour is maintained during the restructuring. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. behavior (since the rest of the lesson is american english, rather than british, we should probably keep it consistent.) |
||
|
||
Often true unit tests, as described in the previous page, test the implementation and may require changing at the same time as the code during a refactor. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. line length. |
||
|
||
## Unittest TestCases | ||
|
||
unittest is part of the Python core library; it defines a class, unittest.TestCase which provides lots of useful machinery for writing tests. We define classes which inherit from unittest.TestCase, bringing in their useful behaviour for us to apply to our tests. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. line length |
||
|
||
The reuse of objects in this way is a really useful programming approach. Don't worry if it is new to you. For testing with unittest, you can just think of it as a framework to fit your test cases into. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. line length |
||
|
||
We can rewrite the tests from the previous page, using the Unittest approach. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. line length. Otherwise, great transition. |
||
|
||
~~~ {.python} | ||
import unittest | ||
|
||
def mean(num_list): | ||
try: | ||
return sum(num_list)/len(num_list) | ||
except ZeroDivisionError : | ||
return 0 | ||
except TypeError as detail : | ||
msg = ("The algebraic mean of an non-numerical list is undefined." | ||
" Please provide a list of numbers.") | ||
raise TypeError(detail.__str__() + "\n" + msg) | ||
|
||
class TestMean(unittest.TestCase): | ||
def test_ints(self): | ||
num_list = [1,2,3,4,5] | ||
obs = mean(num_list) | ||
exp = 3 | ||
self.assertEqual(obs, exp) | ||
|
||
def test_zero(self): | ||
num_list=[0,2,4,6] | ||
obs = mean(num_list) | ||
exp = 3 | ||
self.assertEqual(obs, exp) | ||
|
||
def test_double(self): | ||
# This one will fail in Python 2 | ||
num_list=[1,2,3,4] | ||
obs = mean(num_list) | ||
exp = 2.5 | ||
self.assertEqual(obs, exp) | ||
|
||
def test_long(self): | ||
big = 100000000 | ||
obs = mean(range(1,big)) | ||
exp = big/2.0 | ||
self.assertEqual(obs, exp) | ||
|
||
def test_complex(self): | ||
num_list = [2 + 3j, 3 + 4j, -32 + 2j] | ||
obs = mean(num_list) | ||
exp = -9 + 3j | ||
self.assertEqual(obs, exp) | ||
|
||
if __name__ == '__main__': | ||
unittest.main() | ||
~~~ | ||
|
||
The naming of `class` and `def` entities is important. All classes should be named beginning `Test` and all methods should be named beginning `test-`. This enables the test runner to identify test cases to run. | ||
|
||
Save this code to a file, e.g. `test_mean.py`, again beginning the name with `test_`. This module can be run directly with python: | ||
|
||
~~~ {.bash} | ||
python test_mean.py | ||
~~~ | ||
|
||
~~~ {.output} | ||
..... | ||
---------------------------------------------------------------------- | ||
Ran 5 tests in 2.053s | ||
|
||
OK | ||
|
||
~~~ | ||
|
||
Unittest reports failures and errors on test cases, which we can see if we run Python2, as one of our tests only passes in Python3: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. line length |
||
|
||
~~~ {.bash} | ||
python2 test_mean.py | ||
~~~ | ||
|
||
~~~ {.output} | ||
.F... | ||
====================================================================== | ||
FAIL: test_double (__main__.TestMean) | ||
---------------------------------------------------------------------- | ||
Traceback (most recent call last): | ||
File "test_mean.py", line 31, in test_double | ||
self.assertEqual(obs, exp) | ||
AssertionError: 2 != 2.5 | ||
|
||
---------------------------------------------------------------------- | ||
Ran 5 tests in 2.433s | ||
|
||
FAILED (failures=1) | ||
~~~ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
line too long. Please keep lines, even text, under 80 chars.