The other week I did a half-day tutorial at the Targeting Quality conference on using Python for automating Web Service testing. The rationale for this is that too many people are testing too much stuff in the browser — which is big and slow. Yes, the person who makes a living automating browsers says that it is big and slow. A key point of working with Selenium is knowing when not to use it.
Anyhow. The workshop was ok, and the main thing I would change was entirely my fault. The original idea was to show some code then have people work on their own services on their laptops. But I didn’t state that; at all, let alone not clearly enough. As a result, it was just me talking about code for the whole time. It still ‘worked’ but could have been much better I think as more of a workshop.
There were no slides, but we started talking about HTTP. If you are testing anything on the web, you really need to have a handle on how information flows back and forth between the bits.
- The HTTP Standard at the IETF
- HTTP Response Codes as cats
The response codes are especially important when doing web services since they tend to be how information is communicated. Especially with REST based ones.
And speaking of REST, it is one of the two types of web services that I talked about. For this I part I used the Flickr Photo Search API.
For automating this web service I used Py.Test with (so incredibly) awesome Requests module. Web service automation is one of those places where data driving becomes an excellent strategy. Because every runner does runtime data injection differently, one needs to look at Py.Test’s parametrize documentation. Once you have your brain wrapped around how to data drive it, I suggest you look at driving it via an external means so you can update the test data without having to touch the script itself; csv, Python Database API Specification v2.0. When driving from a database, I try to reach into the actual database being used for testing to grab random data that meets the characteristics that I am trying to use. If I am using something like Hexawise to reduce my combinatorial complexity down I would store that in a csv.
Whether your data-drive it or not, you need to parse the response and decide whether or not you got the information you expected. Python comes with XML parsing tools, but are not quite a joy to work with. Beautiful Soup may not be a complete joy, but it is better than the built-in stuff and is pretty pythonic in its implementation.
All the scripts are available on Github but here is the ‘basic’ example.
import py
import pytest
import requests
from bs4 import BeautifulSoup
key = "994124a9812d7a63972e89fc2145b9bc"
secret = "d8761d47cb245888"
url = "http://api.flickr.com/services/rest/"
class TestPhotoSearch(object):
def setup_method(self, method):
self.payload = {
# required for everything
"method": "flickr.photos.search",
# required for this method
"api_key": key
}
def test_thumbtack(self):
self.payload["text"] = "thumbtack"
print(self.payload)
r = requests.get(url, params=self.payload)
print(r)
print(r.text)
if r.status_code == requests.codes.ok:
soup = BeautifulSoup(r.text, features="xml")
if soup.rsp['stat'] == 'ok':
assert(soup.photos["total"] > 0)
else:
pytest.fail(msg = 'Did not get OK from Flickr')
else:
pytest.fail(msg = 'Did not get OK from server')
Oh. And I disabled my API key so it won’t work by just cloning the repo without first getting your own key. Were these actually part of a ‘real’ project I would externalize the keys to some common file so all your scripts don’t have to change if the keys change.
The other category of web service we did was the SOAP/WSDL-y type. These are not nearly as fun to interact with but have a bunch of features like encryption and digital payload signatures which are appealing in certain situations. And it occurred to me that their popularity in Java/.NET is not unsurprising since they are static typed languages and WSDLs enforce the type of parameters passed around in the request/response.
My original idea was to use the same API for for both types of web service but that didn’t really pan out. So for this I used the Bing API, Version 2. The trick for dealing with this type of service is to limit how much SOAP Envelope construction you have to do by hand. To hopefully zero. In Python the module I use for this is Suds which actually has some tragically [newbie] unfriendly documentation. But seeing an example is useful. So here is the example.
import pytest
from suds.client import Client
import logging
logging.basicConfig(level=logging.INFO)
logging.getLogger('suds.client').setLevel(logging.DEBUG)
application_id = "0E100A06A5C6822E953B7F954C568BA6437FA918"
class TestBingSearch(object):
def setup_method(self, method):
self.client = Client('http://api.bing.net/search.wsdl?AppID=%s&Version=2.2' % application_id)
self.SearchRequest = self.client.factory.create('SearchRequest').parameters
# print(self.SearchRequest)
self.SearchRequest["AppId"]= application_id
def test_thumbtack(self):
self.SearchRequest["Query"] = "thumbtack"
adult = self.client.factory.create('AdultOption')
adult = "Strict"
self.SearchRequest["Adult"] = adult
source_types = self.client.factory.create('ArrayOfSourceType')
source_types.SourceType.append("Web")
self.SearchRequest["Sources"] = source_types
# print(self.SearchRequest)
SearchResponse = self.client.service.Search(self.SearchRequest)
# print(SearchResponse)
assert(SearchResponse.Web.Total > 0)
Data driving this type of service is exactly the same as with a REST one so I didn’t write that.
At this point it is just about practice. And even if your application doesn’t have web services now, it will likely grow them at some point so learning how to do it useful. The programmable web is a useful resource for free APIs that you can mess about with.
Post a Comment