At first glance, this script appears to be rather similar to a recorded grinder-test-script, but there are a few important differences:
The first thing to note is that the headers are not global variables. Instead, they are class variables now. If you'll recall, the TestRunner class in the recorded test-script had methods that corresponded to each recorded page. The situation is similar here. The LoginTask class also has methods that correspond to each recorded page (you might be wondering why they are called 'appLoginN' instead of the non-descriptive 'pageN', but I'll get to that later). Finally, instead of the __call__ method, LoginTask has a run() method (in retrospect, I guess I could have made the class callable, but it didn't seem necessary. I'm also note that familiar with Python and I wanted to avoid any unpleasant surprises).
Now let's go over the important aspects of this class:
The constructor for LoginTask is pretty simple. In the very first line, we call the parent class's constructor, and then we give this class a description. In the second line, the urlDict property is set to an empty dictionary. You might wonder why we aren't actually setting any values for the URLs. The reason is that we want the parent Scenario to define the URLs for its child tasks. Therefore, the tasks themselves will not have any default URLs. Instead, they will inherit the URL values from the Scenario that they belong to. The last line is pretty important. You'll recall that in the constructor of the Task class, the class variable numberOfTasks was incremented by one. Here, we take that value and assign it to the taskId attribute of LoginTask. What we're essentially doing is giving each instantiated task a unique id. You'll see why that's important in a little bit.
This is probably the most important method. Here we set up data for our task and strangely enough, initialize our instance. The first thing we do is set up our requests. You'll notice that the manner in which we do this is pretty similar to the way that the recorded test-script does it. However, in our case, for the URL values we use the urlDict dictionary, and the headers are class instance variables. Another important thing to notice is the first argument to the Test class's constructor. You'll notice that it is an expression that includes self.taskId. The reason we use the id of the task is because it is unique. Tests in Grinder need to have unique numbers. Remember that we want our tasks to be reusable. So it is conceivable to have a situation where we are using more than one instance of LoginTask. If we didn't give each test a unique id and simply used the format [pageNumber][requestNumber] then there would be no way for Grinder to distinguish between tests and our results wouldn't make very much sense. Therefore, our test numbers will be of the format [taskId][pageNumber][requestNumber]
After initializing our requests, we set up the parameters dictionary. This dictionary is important in two ways. First, it maintains a list of name-value pairs that are used in POSTs and GETs, and second, it helps us parameterize those values (by letting us directly modify it). In the initializeTask method, the parameters dictionary is initially set up with values collected by the recorder. This way, we have sensible defaults. The parameters dictionary is a 3-level dictionary. The very first level has keys that correspond to method names within the class. The second level consists of keys that match up with request numbers. The final level contain the parameter names themselves (used in GETs or POSTs). So, to access a parameter value you essentially do this: parameters[methodName][requestNumber][parameterName]. To understand the rationale behind this design, think back to the diagram that describes the relationship between requests and tasks. A set of requests belong to a page, so it makes sense to have the top level of the hash to be method names (since they correspond to each recorded page). Then, the bottommost level will contain parameter names that belong to a particular request.
The last set of statements are similar to the calls to instrumentMethod in a typical recorded-script. Notice, however, that once again we use the taskId property to ensure that we have unique test numbers.