Writing Performance Tests in Grinder using a Framework

by vivin

Page methods

Similar to a typical recorded test-script, the page methods of the LoginTask class correspond to each unique page that was recorded during the task. The code in these methods is very similar to the one in a recorded test-script. However, in the original script, parameter values within a querystring (for GETs) were pulled out into “tokens” and were turned into instance variables of the TestRunner class. For example, the querystring parameter msg would become self.token_msg. In our framework, however, the querystring parameters and their default values are stored in the parameters dictionary and accessed the same way that POST parameters and values are.

run() method

The run() method is similar to the __call()__ method in a recorded test-script. The only difference is that call the callParameterizingMethodFor method before we actually call the page method. You should now be able to see how we can easily parameterize the values used by requests in a page method. The parameterizing method will modify the values in the parameters hash, and these values will be used by the requests in the corresponding page method.

Hopefully you now have a clearer picture of the framework. You should now be able to see how we can design tasks to be discrete units that we can reuse, customize, and parameterize to our wishes. Even better: you don’t have to write the code for these tasks from scratch. I’ve written a translator script in Perl that converts the XML data recorded by TCPProxy and converts it into a class like the one above (I’ll talk about that later). Now that you’ve seen how a task is designed, let’s go up one level and take a look at the Scenario class.

The Scenario class

The Scenario class (as the name suggests) describes a particular scenario. It contains a list of tasks that it will execute sequentially. It looks like this:

Scenario.py

import warnings

class Scenario:
      def __init__(self, description, urlDict):
          if(len(urlDict.keys()) == 0):
             raise Exception("Cannot set " + self.__class__.__name__ + ".urlDict to an empty dictionary.")
          else:
             self.description = description
             self.urlDict = urlDict
             self.tasks = []

      def addTask(self, task):
          if(hasattr(task, "setUrlDict")):
             task.setUrlDict(self.urlDict)
          else:
             raise Exception(task.__class__.__name__ + " does not implement setUrlDict()!")

          if(hasattr(task, "initializeTask")):
             task.initializeTask()
          else:
             raise Exception(task.__class__.__name__ + " does not implement initializeTask()!")

          if(hasattr(task, "run")):
             self.tasks.append(task)
          else:
             raise Exception(task.__class__.__name__ + " does not implement run()!")

      def setUrlDict(self, url):
          warnings.warn("Scenario.urlDict is a read-only property that can only be initialized in the constructor.")

      def getUrlDict(self):
          return self.urlDict

      def setUrl(self, key, value):
          warnings.warn("Cannot modify Scenario.urlDict because it is a read-only property")

      def getUrl(self, key):
          return self.urlDict[key]

      urlDict = property(getUrlDict, setUrlDict)

      def run(self):
          for task in self.tasks:
              if(hasattr(task, "run")):
                 grinder.logger.output("Running " + task.__class__.__name__ + " now!", grinder.logger.TERMINAL)
                 task.run()
              else:
                 raise Exception(task.__class__.__name__ + " does not implement run()!")