Test YOUR CODE


 

Who AM I?

Ratnadeep Debnath


Nick: rtnpro
rtnpro@gmail.com
FOSS contributor:

Currently working at
 

WHY TEST?




with a solid test suite, you can make big changes, confident that the externally visible behavior will remain the same

-- Nick Coghlan





"Code without tests is broken by design."
                                                                          - Jacob

WHY?



  • Prevent regressions
  • Prevent fear
  • Prevent bad design

Setup your environment



$ sudo pip install virtualenvwrapper
$ virtualenvwrapper.sh
$ mkvirtualenv test --no-site-packages
$ workon test
$ pip install nose coverage mock Django django-nose


Virtualenv


$ sudo pip install virtualenv
$ virtualenv test --no-site-packages
$ ~/.virtualenvs/test/bin/activate 


VIRTUALENVWRAPPER
sudo pip install virtualenvwrapper
mkvirtualenv test --no-site-packages
workon test 


$ pip install nose

$ pip install coverage 

$ pip install mock


Django

$ pip install Django
# Install django-nose $ pip install django-nose


Types OF TESTS

INTEGRATION
SYSTEM
DOCTEST
UNITTEST

doctest


The doctest module searches for pieces of text that look like interactive Python sessions, and then executes those sessions to verify that they work exactly as shown.

WHEN TO USE?

  • To check that a module’s docstrings are up-to-date by verifying that all interactive examples still work as documented.
  • To perform regression testing by verifying that interactive examples from a test file or a test object work as expected.
  • To write tutorial documentation for a package, liberally illustrated with input-output examples. Depending on whether the examples or the expository text are emphasized, this has the flavor of “literate testing” or “executable documentation”.

Example

factorial.py
def fact(n):
    """
    Factorial function

    :arg n: Number
    :returns: factorial of n

    >>> fact(0)
    1
    >>> fact(4)
    24
    """
    if n == 0:
        return 1
    return n * fact(n -1) 


RUN DOCTEST


$ python -m doctest factorial.py 
         Alternatively,
# You can add the following at the end of the file
if __name__ == '__main__':
    import doctest
    doctest.test_method()
# Execute tests from shell as below.
$ python factorial.py

UNITTEST



In computer programming, unit testing is a method by which individual units of source code, sets of one or more computer program modules together with associated control data, usage procedures, and operating procedures, are tested to determine if they are fit for use.
                                                                           -- Wikipedia

Example CODE

factorial.py
import sys

def fact(n):
    """
    Factorial function

    :arg n: Number
    :returns: factorial of n

    """
    if n == 0:
        return 1
    return n * fact(n -1)

def div(n):
    """
    Just divide
    """
    res = 10 / n
    return res


def main(n):
    res = fact(n)
    print res

if __name__ == '__main__':
    if len(sys.argv) > 1:
        main(int(sys.argv[1])) 
$ python factorial.py
120 

SAMPLE TEST

test_factorial.py
import unittest
from factorial import fact class TestFactorial(unittest.TestCase):     """     Our basic test class     """     def test_fact(self):         """         The actual test.         Any method which starts with ``test_`` will considered as a test case.         """         res = fact(5)         self.assertEqual(res, 120)

if __name__ == '__main__':     unittest.main()

Running TEST


$ python factorial_test.py
.
----------------------------------------------------------------------
Ran 1 test in 0.000s

OK 

          Alternatively...
$ python -m unittest factorial_test


HANDS ON


Let's try to write test for FizzBuzz :)

UNITTEST *MODULE*


Basic Structure

import unittest

class SampleTestCase(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        # setup environment persistent for the entire test case run        ...    def setUp(self):
        # setup environment for each test method        ...    def test_case1(self):
        # some test code        ....
    ...    def tearDown(self):
        # Undo changes made in setUp()        ...    @classmethod
    def tearDownClass(cls):
        # Undo changes made in setUpClass()        ...

BaSIC ASSERTIONS


Method	Checks that	New in
assertEqual(a, b)	a == b	 
assertNotEqual(a, b)	a != b	 
assertTrue(x)	bool(x) is True	 
assertFalse(x)	bool(x) is False	 
assertIs(a, b)	a is b	2.7
assertIsNot(a, b)	a is not b	2.7
assertIsNone(x)	x is None	2.7
assertIsNotNone(x)	x is not None	2.7
assertIn(a, b)	a in b	2.7
assertNotIn(a, b)	a not in b	2.7
assertIsInstance(a, b)	isinstance(a, b)	2.7
assertNotIsInstance(a, b)	not isinstance(a, b)	2.7 

NOSE


nose extends unittest to make testing easier.

HANDS ON


# nosetests finds all tests in the current directory
# and runs them.
$ nosetests
# You can also run tests from a particular module/package$ nosetests factorial.test_factorial
# You can even run a particular Test Case$ nosetests factorial.test_factorial:TestFactorial
# Or a test method$ nosetests factorial.test_factorial:TestFactorial.test_fact

COVERAGE


Coverage.py is a tool for measuring code coverage of Python programs.


  • monitors your program, noting which part of code got executed.
  • also, finds out which executable code was not executed.
  • works as a gauge to measure the effectiveness of test cases.

HANDS ON

Using coverage:
$ coverage -x test_factorial.py
$ coverage -rm
Name             Stmts   Miss  Cover   Missing
----------------------------------------------
factorial           14      6    57%   25-26, 30-31, 35-36
test_factorial       8      0   100%   
----------------------------------------------
TOTAL               22      6    73%  


NOSE COVERAGE

$ cd ~/test-your-code
$ nosetests --with-coverage --cover-erase --cover-tests
.......
Name                                                  Stmts   Miss  Cover   Missing
-----------------------------------------------------------------------------------
test_your_code                                            0      0   100%   
test_your_code.examples                                   0      0   100%   
test_your_code.examples.factorial                         0      0   100%   
test_your_code.examples.factorial.factorial              14      6    57%   25-26, 30-31, 35-36
test_your_code.examples.factorial.test_factorial          8      1    88%   19
test_your_code.examples.fizzbuzz                          0      0   100%   
test_your_code.examples.fizzbuzz.fizzbuzz                15      1    93%   30
test_your_code.examples.fizzbuzz.test_fizzbuz            15      1    93%   24
test_your_code.examples.fizzbuzz.test_fizzbuzz_main       9      0   100%   
-----------------------------------------------------------------------------------
TOTAL                                                    61      9    85%   
----------------------------------------------------------------------
Ran 7 tests in 0.026s

OK 


HANDS ON

Let's write test for the following:
import sys, requests, json

URL = "https://api.github.com/users/{user}/repos"

def most_watched_repos(user, n):
    url = URL.format(user=user)
    resp = requests.get(url, timeout=10)
    repos_list = json.loads(resp.content)
    return [i['html_url'] for i in sorted(
        repos_list, key=lambda i: -1 * i['watchers'])][:n]


if __name__ == '__main__':
    user = sys.argv[1]
    n = int(sys.argv[2])
    print most_watched_repos(user, n) 

MOCK


Mock is a flexible mock object intended to replace the use of stubs and test doubles throughout your code


Some Facts

  • Mocks are callable and create attributes as new mocks when you access them.
  • Accessing the same attribute will always return the same mock.
  • Mocks record how you use them, allowing you to make assertions about what your code has done to them.

BASIC usage

>>> import mock
>>> m = mock.Mock()
>>> m.called
False
>>> m.call_count
0
>>> m()
>>> m.called
True
>>> m.call_count
1 >>> m.return_value = 'foo'>>> m()'foo'>>> m.side_effect = Exception('Boom!')
>>> m()
Traceback (most recent call last):
  ...
Exception: Boom!

MoCK Assertions

assert_called_with
>>> mock = Mock()
>>> mock.method(1, 2, 3, test='wow')

>>> mock.method.assert_called_with(1, 2, 3, test='wow') 

assert_called_once_with
>>> mock = Mock(return_value=None)
>>> mock('foo', bar='baz')
>>> mock.assert_called_once_with('foo', bar='baz')
>>> mock('foo', bar='baz')
>>> mock.assert_called_once_with('foo', bar='baz')
Traceback (most recent call last):
  ...
AssertionError: Expected to be called once. Called 2 times. 

Mock assertions

assert_any_call
>>> mock = Mock(return_value=None)
>>> mock(1, 2, arg='thing')
>>> mock('some', 'thing', 'else')
>>> mock.assert_any_call(1, 2, arg='thing') 

Reset MOCK

>>> mock = Mock(return_value=None)
>>> mock('hello')
>>> mock.called
True
>>> mock.reset_mock()
>>> mock.called
False 

patch


The patch decorators are used for patching objects only within the scope of the function they decorate. They automatically handle the unpatching for you, even if exceptions are raised. All of these functions can also be used in with statements or as class decorators.

PATCH


>>> @patch('__main__.SomeClass')
... def function(normal_argument, mock_class):
...     print mock_class is SomeClass
...
>>> function(None)
True 


HANDS ON


Let's try to make improve our test for most_watched_repos().

Get rid of mocks :)


Make your code more data dependent.

Let's refactor most_watched_repos().




Testing WEB APPS




Questions?




Thank You



Test YOUR CODE

By Ratnadeep Debnath

Test YOUR CODE

  • 1,659