KEMBAR78
Unit Testing in Python | PDF
Unit Testing in Python
Haim Michael
August 1st
, 2022
All logos, trade marks and brand names used in this presentation belong
to the respective owners.
life
michae
l
Quality Assurance Automation
www.lifemichael.com
© 2008 Haim Michael 20150805
What is Unit Testing?
© 2008 Haim Michael 20150805
What is Unit Testing?
 Unit test is a way of testing a unit (the smallest piece of code
that can be logically isolated in a system). In most
programming languages, that is a function, a subroutine, a
method or a property.
© 2008 Haim Michael 20150805
The TDD Methodology
 Test Driven Development (TDD) is a software development
methodology that focuses on creating unit test cases before
developing the actual code.
© 2008 Haim Michael 20150805
The TDD Methodology
 TDD leads to repetitive work flow in which we write the test,
write the code to be tested, execute the test and repeat these
two steps till the test passes.
Image Credit: TutorialPoint.com
© 2008 Haim Michael 20150805
The pytest Framework
© 2008 Haim Michael 20150805
The pytest Framework
 The pytest framework assists us with the development of
small readable tests. In addition, we can also use this library
when developing complex functional tests.
https://pytest.org
© 2008 Haim Michael 20150805
Installing The pytest Framework
 We can easily install this framework using the pip utility.
pip install -U pytest
 We can easily check the version of the pytest library we have
just installed using the following command:
pytest --version
Upgrading to Newest Version
© 2008 Haim Michael 20150805
Using The pytest Framework
 We can easily use the pytest framework by executing the
utility. Doing so, all files of the form test_*.py or
_test.py in the current directory and its subdirectories will
be examined, and all tests (functions their names start with
test_) will be called.
pytest
© 2008 Haim Michael 20150805
Simple Code Sample
def total(num1: int, num2: int) -> int:
result = num1 + num2
return result
def multiply(num1: int, num2: int) -> int:
result = num1 * num2
return result
utils.py
© 2008 Haim Michael 20150805
Simple Code Sample
import utils
def test_total():
actual_value = utils.total(3, 5)
expected_value = 8
assert actual_value == expected_value
def test_multiply():
actual_value = utils.multiply(3, 5)
expected_value = 15
assert actual_value == expected_value
test_utils.py
© 2008 Haim Michael 20150805
Simple Code Sample
© 2008 Haim Michael 20150805
Grouping Multiple Tests in Class
 We can group our tests in a class. We just need to define
each and every function with a name that starts with test_.
 There is no need to define a class that extends a specific
class that already exists. We just need to make sure that the
name of our class starts with Test.
© 2008 Haim Michael 20150805
Simple Code Sample
class BankAccount:
def __init__(self,id,balance):
self.id = id
self.balance = balance
def deposit(self,sum):
self.balance += sum
def withdraw(self,sum):
self.balance -= sum
banking.py
© 2008 Haim Michael 20150805
Simple Code Sample
import banking
class TestBankAccount:
def test_deposit(self):
ob = banking.BankAccount(123123,1000)
ob.deposit(80)
ob.deposit(120)
ob.deposit(400)
assert ob.balance == 1600
def test_withdraw(self):
ob = banking.BankAccount(123123,1000)
ob.withdraw(80)
ob.withdraw(120)
ob.withdraw(400)
assert ob.balance == 400
test_banking.py
© 2008 Haim Michael 20150805
Simple Code Sample
© 2008 Haim Michael 20150805
Assert Exceptions Raising
 We can assert whether a specific function raised an exception
using the raises helper.
© 2008 Haim Michael 20150805
Simple Code Sample
class BankAccount:
def __init__(self, id, balance):
self.id = id
self.balance = balance
def deposit(self, sum):
self.balance += sum
def withdraw(self, sum):
if sum<self.balance:
self.balance -= sum
else:
raise BankException("balance cannot be negative")
class BankException(Exception):
def __init__(self, message):
super().__init__(self, message)
banking.py
© 2008 Haim Michael 20150805
Simple Code Sample
import banking
import pytest
class TestBankAccount:
def perform_withdraws(self):
ob = banking.BankAccount(123123, 100)
ob.withdraw(80)
ob.withdraw(10)
ob.withdraw(30)
def test_withdraw(self):
with pytest.raises(banking.BankException):
self.perform_withdraws()
test_banking.py
© 2008 Haim Michael 20150805
Simple Code Sample
© 2008 Haim Michael 20150805
The requests Library
© 2008 Haim Michael 20150805
The Requests Library
 Requests is a simple HTTP library for Python. It allows us to
send HTTP requests.
https://docs.python-requests.org
© 2008 Haim Michael 20150805
Installation of Requests
 We can easily install Requests by using the pip command:
pip install requests
© 2008 Haim Michael 20150805
The GET Request
 We can easily initiate a GET request by calling the
requests.get() method.
© 2008 Haim Michael 20150805
The GET Request
import requests
data = requests.get("https://api.coinbase.com/v2/currencies")
print(data.status_code)
print(data.content)
print(data.text)
print(data.json()) #dict object
print(data.headers) #dict object
© 2008 Haim Michael 20150805
The GET Request
© 2008 Haim Michael 20150805
Query String Parameters
 We can easily customize the GET request, and send query
string parameters in the URL, by passing over a reference for
a dict object that holds those parameters.
© 2008 Haim Michael 20150805
Query String Parameters
import requests
response = requests.get(
"https://api.github.com/search/repositories",
params={'q': 'requests+language:python'})
data = response.json()
print(data)
© 2008 Haim Michael 20150805
Request Headers
 We can easily customize the headers that are sent together
with the request by passing over a reference for a dict object
that holds the headers we want to customize. The names of
the headers are the keys, and the values of the headers are
the values of those keys.
© 2008 Haim Michael 20150805
Request Headers
import requests
response = requests.get(
"https://api.github.com/search/repositories",
params={'q': 'requests+language:python'},
headers={'sec-ch-ua-platform': 'macOS'})
data = response.json()
print(data)
© 2008 Haim Michael 20150805
Other HTTP Methods
 In addition to the get() method, The Requests library
provides us with other similar methods for each one of the
other HTTP methods.
© 2008 Haim Michael 20150805
Other HTTP Methods
import requests
response = requests.post('https://httpbin.org/post',
data={'key':'value'})
print(response.json())
response = requests.put('https://httpbin.org/put',
data={'key':'value'})
print(response.json())
response = requests.delete('https://httpbin.org/delete')
print(response.json())
© 2008 Haim Michael 20150805
Other HTTP Methods
© 2008 Haim Michael 20150805
Introspecting The Request
 When making the request, the Requests library prepares the
request before sending it.
 We can introspect the request by referring the request
attribute of the response object.
© 2008 Haim Michael 20150805
Introspecting The Request
import requests
response = requests.post('https://httpbin.org/post', data={'key':'value'})
print(response.request.headers['Content-Type'])
print(response.request.url)
print(response.request.body)
© 2008 Haim Michael 20150805
REStful Web Services Testing
 We can easily write unit tests for REStful web services using
the PyTest unit testing framework and the Requests library.
© 2008 Haim Michael 20150805
REStful Web Services Testing
import requests
def test_get_status_code_ok_from_httpbin():
response = requests.get(
'https://httpbin.org/get',
data={'key': 'value'})
assert response.status_code == 200
© 2008 Haim Michael 20150805
Best Practices
08/01/22 © Abelski eLearning 39
Readable Code
 The unit tests we develop should be updated in accordance
with the changes that take place in the code we test.
 Having readable code will eases the update of the unit tests
code, either by us or by others.
08/01/22 © Abelski eLearning 40
Deterministic Tests
 It would be better if we use deterministic data instead of
randomized generated one.
 Deterministic tests either pass in all executions or not. They
exhibit the same behavior every time they run.
08/01/22 © Abelski eLearning 41
Avoid Interdependencies
 We better have each test case with its own setup and
teardown mechanism in order to avoid interdependencies
between the tests.
08/01/22 © Abelski eLearning 42
Avoid Logic
 Writing unit tests with logical conditions, manual strings
concatenation and various calculations might increase the
risk for bugs in our unit tests. The tests should focus on the
expected result.
08/01/22 © Abelski eLearning 43
Implementing TDD
 When we develop our unit tests before we develop the code
the quality of our tests improves.
08/01/22 © Abelski eLearning 44
CI/CD Integration
 Integrating our unit tests into the CI/CD pipeline will allow us
to run them several times per day (automatically). Doing so
will improve the quality of our code.
08/01/22 © Abelski eLearning 45
Update Our Tests
 Maintaining and updating the tests periodically will improve
their quality.
© 2009 Haim Michael All Rights Reserved 46
Questions & Answers
Thanks for Your Time!
Haim Michael
haim.michael@lifemichael.com
+972+3+3726013 ext:700
+972+54+6655837 (whatsapp)
life
michael

Unit Testing in Python

  • 1.
    Unit Testing inPython Haim Michael August 1st , 2022 All logos, trade marks and brand names used in this presentation belong to the respective owners. life michae l Quality Assurance Automation www.lifemichael.com
  • 2.
    © 2008 HaimMichael 20150805 What is Unit Testing?
  • 3.
    © 2008 HaimMichael 20150805 What is Unit Testing?  Unit test is a way of testing a unit (the smallest piece of code that can be logically isolated in a system). In most programming languages, that is a function, a subroutine, a method or a property.
  • 4.
    © 2008 HaimMichael 20150805 The TDD Methodology  Test Driven Development (TDD) is a software development methodology that focuses on creating unit test cases before developing the actual code.
  • 5.
    © 2008 HaimMichael 20150805 The TDD Methodology  TDD leads to repetitive work flow in which we write the test, write the code to be tested, execute the test and repeat these two steps till the test passes. Image Credit: TutorialPoint.com
  • 6.
    © 2008 HaimMichael 20150805 The pytest Framework
  • 7.
    © 2008 HaimMichael 20150805 The pytest Framework  The pytest framework assists us with the development of small readable tests. In addition, we can also use this library when developing complex functional tests. https://pytest.org
  • 8.
    © 2008 HaimMichael 20150805 Installing The pytest Framework  We can easily install this framework using the pip utility. pip install -U pytest  We can easily check the version of the pytest library we have just installed using the following command: pytest --version Upgrading to Newest Version
  • 9.
    © 2008 HaimMichael 20150805 Using The pytest Framework  We can easily use the pytest framework by executing the utility. Doing so, all files of the form test_*.py or _test.py in the current directory and its subdirectories will be examined, and all tests (functions their names start with test_) will be called. pytest
  • 10.
    © 2008 HaimMichael 20150805 Simple Code Sample def total(num1: int, num2: int) -> int: result = num1 + num2 return result def multiply(num1: int, num2: int) -> int: result = num1 * num2 return result utils.py
  • 11.
    © 2008 HaimMichael 20150805 Simple Code Sample import utils def test_total(): actual_value = utils.total(3, 5) expected_value = 8 assert actual_value == expected_value def test_multiply(): actual_value = utils.multiply(3, 5) expected_value = 15 assert actual_value == expected_value test_utils.py
  • 12.
    © 2008 HaimMichael 20150805 Simple Code Sample
  • 13.
    © 2008 HaimMichael 20150805 Grouping Multiple Tests in Class  We can group our tests in a class. We just need to define each and every function with a name that starts with test_.  There is no need to define a class that extends a specific class that already exists. We just need to make sure that the name of our class starts with Test.
  • 14.
    © 2008 HaimMichael 20150805 Simple Code Sample class BankAccount: def __init__(self,id,balance): self.id = id self.balance = balance def deposit(self,sum): self.balance += sum def withdraw(self,sum): self.balance -= sum banking.py
  • 15.
    © 2008 HaimMichael 20150805 Simple Code Sample import banking class TestBankAccount: def test_deposit(self): ob = banking.BankAccount(123123,1000) ob.deposit(80) ob.deposit(120) ob.deposit(400) assert ob.balance == 1600 def test_withdraw(self): ob = banking.BankAccount(123123,1000) ob.withdraw(80) ob.withdraw(120) ob.withdraw(400) assert ob.balance == 400 test_banking.py
  • 16.
    © 2008 HaimMichael 20150805 Simple Code Sample
  • 17.
    © 2008 HaimMichael 20150805 Assert Exceptions Raising  We can assert whether a specific function raised an exception using the raises helper.
  • 18.
    © 2008 HaimMichael 20150805 Simple Code Sample class BankAccount: def __init__(self, id, balance): self.id = id self.balance = balance def deposit(self, sum): self.balance += sum def withdraw(self, sum): if sum<self.balance: self.balance -= sum else: raise BankException("balance cannot be negative") class BankException(Exception): def __init__(self, message): super().__init__(self, message) banking.py
  • 19.
    © 2008 HaimMichael 20150805 Simple Code Sample import banking import pytest class TestBankAccount: def perform_withdraws(self): ob = banking.BankAccount(123123, 100) ob.withdraw(80) ob.withdraw(10) ob.withdraw(30) def test_withdraw(self): with pytest.raises(banking.BankException): self.perform_withdraws() test_banking.py
  • 20.
    © 2008 HaimMichael 20150805 Simple Code Sample
  • 21.
    © 2008 HaimMichael 20150805 The requests Library
  • 22.
    © 2008 HaimMichael 20150805 The Requests Library  Requests is a simple HTTP library for Python. It allows us to send HTTP requests. https://docs.python-requests.org
  • 23.
    © 2008 HaimMichael 20150805 Installation of Requests  We can easily install Requests by using the pip command: pip install requests
  • 24.
    © 2008 HaimMichael 20150805 The GET Request  We can easily initiate a GET request by calling the requests.get() method.
  • 25.
    © 2008 HaimMichael 20150805 The GET Request import requests data = requests.get("https://api.coinbase.com/v2/currencies") print(data.status_code) print(data.content) print(data.text) print(data.json()) #dict object print(data.headers) #dict object
  • 26.
    © 2008 HaimMichael 20150805 The GET Request
  • 27.
    © 2008 HaimMichael 20150805 Query String Parameters  We can easily customize the GET request, and send query string parameters in the URL, by passing over a reference for a dict object that holds those parameters.
  • 28.
    © 2008 HaimMichael 20150805 Query String Parameters import requests response = requests.get( "https://api.github.com/search/repositories", params={'q': 'requests+language:python'}) data = response.json() print(data)
  • 29.
    © 2008 HaimMichael 20150805 Request Headers  We can easily customize the headers that are sent together with the request by passing over a reference for a dict object that holds the headers we want to customize. The names of the headers are the keys, and the values of the headers are the values of those keys.
  • 30.
    © 2008 HaimMichael 20150805 Request Headers import requests response = requests.get( "https://api.github.com/search/repositories", params={'q': 'requests+language:python'}, headers={'sec-ch-ua-platform': 'macOS'}) data = response.json() print(data)
  • 31.
    © 2008 HaimMichael 20150805 Other HTTP Methods  In addition to the get() method, The Requests library provides us with other similar methods for each one of the other HTTP methods.
  • 32.
    © 2008 HaimMichael 20150805 Other HTTP Methods import requests response = requests.post('https://httpbin.org/post', data={'key':'value'}) print(response.json()) response = requests.put('https://httpbin.org/put', data={'key':'value'}) print(response.json()) response = requests.delete('https://httpbin.org/delete') print(response.json())
  • 33.
    © 2008 HaimMichael 20150805 Other HTTP Methods
  • 34.
    © 2008 HaimMichael 20150805 Introspecting The Request  When making the request, the Requests library prepares the request before sending it.  We can introspect the request by referring the request attribute of the response object.
  • 35.
    © 2008 HaimMichael 20150805 Introspecting The Request import requests response = requests.post('https://httpbin.org/post', data={'key':'value'}) print(response.request.headers['Content-Type']) print(response.request.url) print(response.request.body)
  • 36.
    © 2008 HaimMichael 20150805 REStful Web Services Testing  We can easily write unit tests for REStful web services using the PyTest unit testing framework and the Requests library.
  • 37.
    © 2008 HaimMichael 20150805 REStful Web Services Testing import requests def test_get_status_code_ok_from_httpbin(): response = requests.get( 'https://httpbin.org/get', data={'key': 'value'}) assert response.status_code == 200
  • 38.
    © 2008 HaimMichael 20150805 Best Practices
  • 39.
    08/01/22 © AbelskieLearning 39 Readable Code  The unit tests we develop should be updated in accordance with the changes that take place in the code we test.  Having readable code will eases the update of the unit tests code, either by us or by others.
  • 40.
    08/01/22 © AbelskieLearning 40 Deterministic Tests  It would be better if we use deterministic data instead of randomized generated one.  Deterministic tests either pass in all executions or not. They exhibit the same behavior every time they run.
  • 41.
    08/01/22 © AbelskieLearning 41 Avoid Interdependencies  We better have each test case with its own setup and teardown mechanism in order to avoid interdependencies between the tests.
  • 42.
    08/01/22 © AbelskieLearning 42 Avoid Logic  Writing unit tests with logical conditions, manual strings concatenation and various calculations might increase the risk for bugs in our unit tests. The tests should focus on the expected result.
  • 43.
    08/01/22 © AbelskieLearning 43 Implementing TDD  When we develop our unit tests before we develop the code the quality of our tests improves.
  • 44.
    08/01/22 © AbelskieLearning 44 CI/CD Integration  Integrating our unit tests into the CI/CD pipeline will allow us to run them several times per day (automatically). Doing so will improve the quality of our code.
  • 45.
    08/01/22 © AbelskieLearning 45 Update Our Tests  Maintaining and updating the tests periodically will improve their quality.
  • 46.
    © 2009 HaimMichael All Rights Reserved 46 Questions & Answers Thanks for Your Time! Haim Michael haim.michael@lifemichael.com +972+3+3726013 ext:700 +972+54+6655837 (whatsapp) life michael