Beginner’s Guide to: Automating API Tests Using Python
May 16th, 2017

Manual testing might be applicable in some cases (or when you’re just beginning your quality assurance engineer path). As the projects become more and more complex and more things should be tested, automated testing becomes your friend. In this article, we’ll take a look at the automating API tests using Python.
Where is this applicable?
- Mobile applications
- Open-source API
- In projects that feature maximum division between the frontend and backend.
Why do you need to automate API testing?
- Minimize regress testing time
- Describe the negative cases for the unstable functionality and in such a way increase the probability of locating the bug
- Quickly identify the system component, in which the changes happened when the test failed
- Minimize human factor when testing
Where do you begin?
In order to set up automated API testing, you need to know:
- Python (at least on the basic level): its syntax, data types, input and output, conditions like if, elif, else. You also should know the methods, lists and vocabularies, cycles, and classes.
- DML (SQL) on the basic level: SELECT, INSERT, UPDATE, DELETE.
- JSON
My personal recommendation would be to catch up on following things (thankfully, there are lots of educational videos on YouTube - we have chosen some, but feel free to look for ones that you like more):
What do you need to have on your computer:
- Python 2.7 or 3.x.x
- Libs for Python: psycopg2, urllib, urllib2, json, coloredlogs
- PyCharm
API Testing Directory Structure
- File .py with pre- and postconditions for entire API testing (for example, database connection settings, base URL, etc.)
- File .py with pre- and postconditions for each test suite (for example, preliminary data recording in the database or database cleanup after the test package launch)
- File .py with test suites for certain features
- File .py with test suites launch instructions
Pre- and postconditions for API testing (example)
In this example, we will perform several actions:
- We create settings class (BaseSettings)
- We declare 2 methods: pre- and postconditions (setUpClass and tearDownClass):
- In the pre-conditions, we declare base URL and connect to the database
- In the postconditions, we display the notification that the testing has finished
from unittest import TestCase
from coloredlogs import install
from psycopg2 import connect
class BaseSettings( TestCase ):
db = None
@classmethod
def setUpClass(cls):
install( level='DEBUG' )
host = 'localhost'
# here you can edit your base url
cls.ApiUrl = "http://localhost:8000/"
# here you can edit your database connection credentials
cls.db = connect( host=host,
user='username',
password='password',
database='password' )
@classmethod
def tearDownClass(cls):
cls.db.close( )
print("------test is over------")
Pre- and postconditions for each test suite (example)
In this example, we
- Create the settings class for the test package (SetUpSettings)
- Declare 2 methods: pre- and postconditions (setUpClass and tearDownClass):
- In the pre-conditions, we display the name of the current test
- In the postconditions, we remove the test data from the database
from logging import getLogger, error
from settings import BaseSettings
class SetUpSettings( BaseSettings ):
def setUp(self):
print('current test: ' + self._testMethodName)
def tearDown(self):
getLogger( 'delete_users' )
db = self.db
cur = db.cursor( )
cur.execute( "SELECT id FROM project.public.core_authuser WHERE username = 'auto_user_2'" )
row_count_2 = cur.rowcount
cur.close( )
if row_count_2 == 0:
cur = db.cursor( )
cur.execute( "SELECT id FROM project.public.core_authuser WHERE username = 'auto_user'" )
row_count = cur.rowcount
cur = db.cursor( )
cur.execute( "SELECT id FROM project.public.core_authuser WHERE username = 'auto_user_1'" )
row_count_1 = cur.rowcount
cur.close( )
if row_count == 1 and row_count_1 == 1:
cur = db.cursor( )
cur.execute( "SELECT id FROM project.public.core_authuser WHERE username = 'auto_user'" )
result = cur.fetchone( )
user_id = result[0]
cur.execute( "SELECT id FROM project.public.core_authuser WHERE username = 'auto_user_1'" )
result = cur.fetchone( )
user_id_1 = result[0]
cur.execute( "SELECT id FROM project.public.core_request WHERE sender_id = " + str(
user_id )
)
result = cur.fetchone( )
if result is None:
cur = db.cursor( )
cur.execute(
"DELETE FROM project.public.authtoken_token WHERE user_id = " + str( user_id )
)
cur.execute(
"DELETE FROM project.public.core_contactlist_contacts WHERE authuser_id = " + str( user_id_1 )
)
cur.execute(
"DELETE FROM project.public.core_contactlist_favorite_contacts WHERE authuser_id = " + str(
user_id_1 )
)
Test Suite structure
We create a class for each of the test suites:
class Registration( SetUpSettings ):
In the class, we create test cases (also known as test methods). The name of the test method should always start with test_
def test_registration(self):
After defining the test methods, we should run them
if __name__ == "__main__":
main( )
API test method structure (test case)
The test method should/may include:
Error log
getLogger( 'registration' )
Test URL
testurl = (self.ApiUrl + 'api-auth' + '/user' + '/signup' + '/')
The data from database
cur = db.cursor( )
cur.execute( 'select key from project.public.authtoken_token' )
row_count = cur.rowcount
cur.close( )
Request parameters
par = {
'phone_code': '+380',
'phone': '1234567890',
'password': 'qwerty',
'username': 'auto_user',
'email': 'auto@user.com',
'avatar': 13
}
Parameters URL-coded into a line
data = urlencode( par )
Request assignment
req = Request( testurl, data )
Headers added into the request
req.add_header( 'Authorization', 'AbC123' )
Request sending
res = urlopen( req )
Asserts and exceptions
# check if response status is 200
try:
self.assertEqual( res.code, 200 )
except:
error( 'Response code is not 200' )
exit( 1 )
Test Suite with One Test Method (Example)
from set_up import SetUpSettings
from json import loads
from logging import getLogger, error
from urllib import urlencode
from urllib2 import Request, urlopen
from unittest import main
class Registration( SetUpSettings ):
def test_registration(self):
getLogger( 'registration' )
db = self.db
testurl = (self.ApiUrl + 'api-auth' + '/user' + '/signup' + '/')
cur = db.cursor( )
cur.execute( 'select key from project.public.authtoken_token' )
row_count = cur.rowcount
cur.close( )
par = {
'phone_code': '+380',
'phone': '1234567890',
'password': 'qwerty',
'username': 'auto_user',
'email': 'auto@user.com',
'avatar': 13
}
data = urlencode( par )
req = Request( testurl, data )
req.add_header( 'Authorization', 'AbC123' )
res = urlopen( req )
# check if response status is 200
try:
self.assertEqual( res.code, 200 )
except:
error( 'Response code is not 200' )
exit( 1 )
text = res.read( )
json_res = loads( text )
# check if the access for request is granted
try:
self.assertTrue( 'response' in json_res )
except:
error( 'Access denied' )
exit( 2 )
response = json_res["response"]
success = response["success"]
# check if the request is succeeded
try:
self.assertEqual( success, 1 )
except:
error( 'Request is failed' )
exit( 3 )
# check if new user have token
try:
self.assertTrue( "token" in json_res )
except:
error( 'Response have no User Token' )
exit( 4 )
cur = db.cursor( )
cur.execute( 'select key from project.public.authtoken_token' )
row_count1 = cur.rowcount
cur.close( )
# check if user is added to database
try:
self.assertTrue( row_count1 - row_count == 1 )
except:
error( 'User is not registered' )
exit( 5 )
if __name__ == "__main__":
main( )
Run all test suites in the directory
- To run the tests we should specify the path to the directory with tests (start_dir)
- Specify the pattern for test suite naming the ones that are run (for example, all test suites names begin with pm_)
from unittest import TestLoader, TextTestRunner
loader = TestLoader()
start_dir = '/Users/username/PycharmProjects/project'
suite = loader.discover(start_dir, pattern='pm_*.py')
runner = TextTestRunner()
runner.run(suite)
Tips and tricks
- Try to move maximum amount of actions into the pre- and postconditions in order to make your tests as independent as possible
- Try to minimize the amount of DML operations in the test method in order to reduce the runtime of tests
- Try to make as few assertions and exceptions into each test method
- Always perform a database check in a positive test case
- Configure PyCharm integration with Git and your database (especially database)
Need help with API development or testing?
