Writing Performance Tests in Grinder using a Framework
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.
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:
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()!")