Thursday, February 10, 2011

Dynamic values within files

I have a HTTP Post request sample to which we are passing an XML file through option "Send Files With Request". The XML file to be sent with request contain on Tag "Order id" which need to be modified [ dynamically during test plan executing] before sending the request. Original
Typically in JMeter we would use a ${variableName} notation. However JMeter only replaces these values when the the ${variableName} is within the script itself , not when it is referenced in some other file. As the user wants to use a separate (template) file which is added to the HTTP request, this technique wont work.
One option to consider is whether we could generate all the files we need before we run the test. this is possible for example when the dynamic data is a set of id's that is accessible say from a database. In this case the better (in the more efficient sense) is to have a initialisation step (perhaps as part of the build or as a separate step in Jmeter) to create all the files that the test would need. Once this is done it is easy to make the JMeter script pick one file (usually using the CSV DataSet Config).
However this isnt always possible. In the question above , the Order ID could be dynamically generated by the system under test in ways that might not be predictable. In this case we cannot generate the files in advance. We have to extend JMeter.
The cleaner more elegant way to do this would be to write a custom pre-processor of some sort because it sounds as if this functionality could be reused in some other context (or extend the HTTPSampler). But we will choose the quick and dirty way of writing a BeanShell preprocessor that does this for us.

Briefly the solution will
a. Have a template XML file. We will use @variableName@ within the file for what we want replaced. We will currently only support replacing a single variable (but it is easy to support multiple variables).
b. We will configure the HTTPSampler to use a generated file. At runtime a pre - processor will read the template file and generate this new file. This new file will be read and posted by JMeter

The Structure of the test is shown below
The HTTP Sampler adds a currently non existent file to be posted. This is the file the Pre Processor will create. The name of the file is varied based on the ThreadNumber (we could have added timestamps etc as well) so that each thread will get its own file name

We use user defined variables to define the location of the temp file, the regex that we want to run on the file and the replacement value.

The BeanShell pre-processor
import java.io.BufferedReader;
import org.apache.jmeter.protocol.http.util.HTTPFileArg;
import java.io.File;
import java.io.FileInputStream;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;
import java.io.BufferedWriter;
import java.io.FileOutputStream;
import java.nio.channels.Channels;

//read the file as string
FileInputStream stream = new FileInputStream(new File(vars.get("templatePath")));
String fileAsString = "";
try {
      FileChannel fc = stream.getChannel();
      MappedByteBuffer bb = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size());     
      fileAsString = Charset.defaultCharset().decode(bb).toString();
}
finally {
        stream.close();
}
//perform the replacement
fileAsString = fileAsString.replaceAll(vars.get("replaceRegex"), vars.get(vars.get("variableName")));

//assumes first file
HTTPFileArg arg = sampler.getHTTPFiles()[0];
//write it out to the file we want
FileChannel fout = new FileOutputStream(arg.getPath()).getChannel();
BufferedWriter bf = new BufferedWriter(Channels.newWriter(fout, Charset.defaultCharset().name()));
bf.write(fileAsString);
bf.close();
Disclaimer : File Reading writing snippets swiped from Google, you can probably make it more efficient.
  • Briefly the snippet reads the template file(location defined as a variable) as a String (using the default charset , if you deal with multiple languages you might want to force this to UTF-8).
  • It then runs the replacement using a regex (also a variable) and the subsitution value (also a variable which for this example we have just spoofed)
  • Finally it writes the file(assuming default charset , again you could force this to be UTF-8). To determine the location of the file , it reads the location from the HTTPSampler. This is currently set as the first file , but we could have used a name or any other technique.
And finally the sampler will read the file that we have created and post it as usual.

Sample JMX available here