03 Test Automation
03 Test Automation
Annex
• Additional testing techniques
Section 1: Getting Started with Testing
Click to edit Master title style
• Setting the scene
• Python test frameworks
• Example class-under-test
• How to write a test
• Example test
• Running tests
• Arrange / Act / Assert
• Testing for exceptions
• Setup and teardown code
Setting the Scene
Click to edit Master title style
• Unit testing verifies the behaviour of code artifacts in
isolation
• In Python, a "unit" is usually a function
def __str__(self):
return f"{self.name}, {self.balance}"
bankAccount.py
How to Write a Test
Click to edit Master title style
• To write tests in PyTest:
• Define Python files named test_xxx.py or xxx_test.py
def test_accountCreated_zeroBalanceInitially():
acc = BankAccount("David")
assert acc.balance == 0
test_BankAccount1.py
• assert is a standard Python keyword
• If the assert test returns false, it throws an
AssertionError
• This causes your test function to terminate immediately
Running Tests
Click to edit Master title style
• To run tests in PyTest, run the following command:
py.test
• Example
def test_singleDeposit_correctBalance():
acc = BankAccount("David")
acc.deposit(100)
assert acc.balance == 100
test_BankAccount1.py
Testing for Exceptions (1 of 2)
Click to edit Master title style
• When you write industrial-strength code, sometimes you
actually want the code to throw an exception
• The code should be robust enough to detect error situations
• You can write a test to verify the code raises an error
import pytest
…
def test_withdrawalsExceedLimit_exceptionOccursV1():
# Arrange.
acc = BankAccount("David")
# Act.
acc.deposit(600)
def test_withdrawalsExceedLimit_exceptionOccursV2():
# Arrange.
acc = BankAccount("David")
# Act.
acc.deposit(600)
@pytest.fixture(autouse=True)
def run_around_tests():
assert_that(… … …)
• You can then use PyHamcrest as
follows in your tests:
Example Class-Under-Test
Click to edit Master title style
def taxPayable(self):
return self.price * 0.20
def __str__(self):
return f"{self.description}, £{self.price}, {self.ratings}"
product.py
Example Test
Click to edit Master title style
• Here's how we can use PyHamcrest to test the class:
from hamcrest import *
import pytest
from Product import Product
product = None
@pytest.fixture(autouse=True)
def run_around_tests():
global product
product = Product("TV", 1500, 5, 4, 3, 5, 4, 3)
yield
def test_product_taxPayable_correct():
assert_that(product.taxPayable(), close_to(300, 0.1))
def test_product_ratings_containsRating():
assert_that(product.ratings, has_item(3))
def test_product_ratings_doesntContainAbsentRating():
assert_that(product.ratings, not(has_item(2))) test_Product1.py
Defining a Custom PyHamcrest Matcher
Click to edit Master title style
• The PyHamcrest matcher model is extensible
• You can define your own custom matcher classes
from hamcrest.core.base_matcher import BaseMatcher
class PriceMatcher(BaseMatcher):
def valid_price():
return PriceMatcher(2500) priceMatcher.py
Using a Custom PyHamcrest Matcher
Click to edit Master title style
• Here's how to use a custom PyHamcrest matcher
• Exactly the same as for the standard PyHamcrest matchers
from hamcrest import *
from priceMatcher import valid_price
from product import Product
def test_product_validPrice_priceAccepted():
product1 = Product("TV", 1500)
assert_that(product1.price, valid_price())
def test_product_negativePrice_priceRejected():
product2 = Product("TV", -1)
assert_that(product2.price, is_not(valid_price()))
def test_product_tooExpensivePrice_priceRejected():
product3 = Product("TV", 2501)
assert_that(product3.price, is_not(valid_price())) test_Product2.py
Section 3: Mocking
Click to edit Master title style
• Overview of mocking
• Mocking in Python
• Mocking values
• Mocking functions
• Mocking methods
Overview of Mocking
Click to edit Master title style
• Real-world systems involve lots of interacting code
• Unit testing focuses on the behaviour of an isolated unit
• We can use mocks to blank off other code
• Mocking
• Use a mocking framework to create a mock
value/function/object
• In tests, use the mock value/function/object rather than a
real one
Mocking in Python
Click to edit Master title style
• The PyUnit module has standard support for mocking
• Via the unittest.mock module
• To install PyTest-mock:
pip install pytest-mock
mockdemo/myDependencies.py
def build_url(**kwargs):
url = BASE_URL
if len(kwargs) != 0:
myCode.py
url += "?"
for k, v in kwargs.items():
• We use the value in our code-under-
url += f"{k}={v}&"
url = url.rstrip('&')
return url
test: mockdemo/myCode.py
Mocking Values (2 of 2)
Click to edit Master title style
• We can mock the value in our test as follows:
• Receive a mocker fixture object (from PyTest-mock)
• Use the mocker object to define a mock value for BASE_URL
import mockdemo.myCode as myCode
def test_build_url(mocker):
expected = 'http://localhost/products?minPrice=100&maxPrice=500&order=desc'
mockdemo/test.py
Mocking Functions (1 of 2)
Click to edit Master title style
def get_meaning_of_life():
result = calculate_meaning_of_life()
return 'The meaning of life is ' + str(result)
mockdemo/myCode.py
def test_get_meaning_of_life(mocker):
mocker.patch(
'mockdemo.myCode.calculate_meaning_of_life',
return_value='wibble'
)
actual = myCode.get_meaning_of_life()
def __init__(self):
def load_product(self):
time.sleep(self.delay)
return 'Bugatti Divo'
def load_great_football_team(self):
time.sleep(self.delay) myDependencies.py
return 'Swansea City' mockdemo/myDependencies.py
def get_product():
dataLoader = DataLoader()
result = dataLoader.load_product()
return 'Product is ' + str(result) mockdemo/myCode.py
Mocking Methods (2 of 2)
Click to edit Master title style
• We can mock methods in our test as follows:
• Receive a mocker fixture object (from PyTest-mock)
• Use the mocker object to mock methods as needed
import mockdemo.myCode as myCode
def test_get_product(mocker):
mocker.patch(
'mockdemo.myCode.DataLoader.load_product',
lambda self: 'wibble'
)
actual = myCode.get_product()
mockdemo/test.py
Click to edit Master title style
Summary
@pytest.mark.parametrize("mark, grade", [
(99, "A*"),
(70, "A"),
(69, "B"),
(60, "B"),
(59, "C"),
(50, "C"),
(49, "D"),
(40, "D"),
(39, "E"),
(30, "E"),
(29, "U")
])
def test_marks_and_grades(mark, grade):
assert grade == get_grade(mark)
test_util.py
Running Tests Selectively
Click to edit Master title style
• py.test lets you specify which test functions to run…
@pytest.mark.numtest
def test_add_numbers():
assert 3 + 4 == 7
@pytest.mark.numtest
def test_multiply_numbers():
assert 3 * 4 == 12
@pytest.mark.strtest
def test_concatenate_strings():
assert "hello " + "world" == "hello world"
@pytest.mark.strtest
def test_uppercase_strings():
assert "hello world".upper() == "HELLO WORLD"
test_setsDemo.py
Grouping Tests into Sets (3 of 3)
Click to edit Master title style
• To run all the tests in a particular set:
• Use the -m option
• Specifies which marked tests to run (i.e., the name of the
set)
py.test -m numtest -v
Test Driven Development (TDD)
Click to edit Master title style
• TDD is a simple concept
• You write the tests first, before your write the code
• The tests are for functionality you're about to implement
• Benefits of TDD
• Helps you focus on functionality rather than implementation
• Ensures every line of code is tested
Refactoring
Click to edit Master title style
• Refactoring is an often-overlooked aspect of TDD
• After each iteration through the test-code-pass cycle, you
should refactor your code
• That is, step back and see if you can/should reorganize your
code to eliminate duplication, restructure inheritance, etc.