Beginner’s Guide to: Automating API Tests Using Python

May 16th, 2017

QA / Mobile // Grossum Possum

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:

  1. We create settings class (BaseSettings)
  2. We declare 2 methods: pre- and postconditions (setUpClass and tearDownClass):
    1. In the pre-conditions, we declare base URL and connect to the database
    2. 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

  1. Create the settings class for the test package (SetUpSettings)
  2. Declare 2 methods: pre- and postconditions (setUpClass and tearDownClass):
    1. In the pre-conditions, we display the name of the current test
    2. 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?

Author: Grossum Possum

Grossum Possum. He loves using Symfony2 for his projects and learning to implement Symfony3. He also likes developing web and mobile applications using other programming languages and frameworks, such as PHP and Java. In his free time, Grossum Possum likes to write about his experiences in the blog.

Tags QA Backend Development Hacks

See all blog

x

Grossum Startup Guide:
Five Things to Consider Before Web & Mobile Development

Get the Guide