Cools ways to code “The Grinder” tests

First off, a shout goes out to The Grinder and their open source team that brought us The Grinder!

I was recently working on a project where I didn’t have a lot of time for script development.  I decided to take a quick look at the script template that Grinder generates after recording test case steps in the browser.  In the Grinder, this tool is called the TCPProxy.  It essentially generates a Jython script that mimics the steps you recorded in the browser.  This script is likely 70-80% functional.  There is a bunch of post-recording steps that you need to tackle before this scripts becomes useful and re-usable.

So, in the end you have a Jython script that needs to be cleaned up before it is useful.  So,  my goal was to find a way to minimize the script recording to help keep the script simple, easy to read and maintain.

For the purpose of this demo, let’s use the following test case:

  1. Navigate to your blog url
  2. Navigate to login page
  3. Login

Now, instead of recording this using The Grinder’s TCPProxy, I simply ran my browser behind a HTTP debugging proxy.  I noticed the HTTP GETs and POSTs.  From that I went with the idea that I’d only have one generic HTTP Get method.

The purpose of the sample code below is to provide a good starting point for others on their journey to learning/mastering The Grinder.  This is not the only way or the best way, just a way that I found useful and extensible and easy-to-read.  In my sample code, the __call__ method was designed to ramp-up traffic slowly over time; basically until a server/application reaches saturation.  I tested this against my own Apache/MySQL/PHP wordpress instance.  I was able to push this pretty hard.  I’ll follow-up with another article dedicated to running this sample and seeing the results.

The Jython code below is the typical Grinder Jython script generated by the TCPProxy. I will provide some samples to show you how you can easily simplify your recording, by removing all the static GET’s with a generic one. Instead of having a hard coded HTTP request object for each test step, we’ll use a Python dictionary to store our Grinder Test objects. The Grinder Test objects are essentially a test case step or single HTTP request.

class TestRunner:
    '''An instance of this class is created for every thread.'''
    def getPage(self, request, url):
        """Generic method that allows you to conduct an HTTP GET."""
        result = request.GET(url)
        return result

Now, when you call that method for each step in your test, you do it like this. Notice that this is the Python dictionary I was talking about earlier. It contains the test objects and test object configuration (data check, url, target hostname etc).


# The __init__ method is called once for each thread.
    def __init__(self):
        self.tests = {
                 "Hit landing page." : {"request":createRequest(Test(101, 'Get Landing page.'), url0, headers0), "url":"/wordpress", "dataCheck":"<title>Title"},
                 "Hit log in page." : {"request":createRequest(Test(201, 'Get Log on page.'), url0, headers0), "url":"/wp-login.php", "dataCheck":"<label for=\"user_login\">Username<br />" },
                 "Log in" : {"request":createRequest(Test(301, 'Log in'), url0, headers0), "url":"/wp-login.php", "dataCheck":"" },
 
}

The code below is the code that each Grinder thread will execute until grinder.duration or grinder.runs is reached. Keep in mind this is just a sample and the sample ramps up users until you tell it to stop. For my test I ran this code as a saturation test to tune my apache/php settings. After testing this I soon realized that I need to have some SLM’s in place to avoid denial-of-service (DoS) or distributed denial-of-service (DDoS) attacks.


    def __call__(self):
 
        grinder.statistics.delayReports = 1
 
        # Use thread with tid 0 for programmatic thread creation.
        # Every 10 seconds, create a new thread.
        if grinder.getThreadNumber() == 0:
            grinder.logger.info("Grinder thread number: %d" % grinder.getThreadNumber())
            # sleep
            grinder.sleep(self.thinkTime)
            # create new thread
            grinder.startWorkerThread()
        else:
            # Write some stuff to the Grinder agent log.
            grinder.logger.info("Grinder thread number: %d" % grinder.getThreadNumber())
            # Get the desired test configuration from the test dictionary.
            test = self.tests["Hit landing page."]["request"]
            url = self.tests["Hit landing page."]["url"]
            dataCheck = self.tests["Hit landing page."]["dataCheck"]
            # Send the request over the wire.
            self.getPage(test, url)
            # A validation or checkpoint in the test.
            if not dataCheck(httpUtilities.getLastResponse(),
                       self.dataCheck,
                       "grinderThreadExample", "getPage", 200):
                grinder.logger.error("HTTP response assertion failed.")
                # Let's allow for some thread exit control.  Desire to control if a 
                # thread should exit on continue on error.
                raise AbortRunException
            # Think time
            grinder.sleep(self.thinkTime)

That concludes a basic example of how you can trim down a large Grinder recording. This is only an example and by no means the best way; but I found this method to cut down on the size and complexity of a Grinder Jython script.

You’ll notice that I did away with the grinder typical request/headers definitions. These are still someone present, though now stored in a python dictionary. The idea there is that you can retrieve the desired test configuration by a user friendly key like “Hit landing page”.

More sample and examples to come. Next post will be about running this sample Grinder test against your own blog; to help identify and prove out optimal tuning.

You must be logged in to post a comment.

Proudly powered by WordPress   Premium Style Theme by www.gopiplus.com
WordPress Appliance - Powered by TurnKey Linux