Rick.Brown

So you want to keep count in a virtual service?

Blog Post created by Rick.Brown Employee on Dec 1, 2016

I get this requirement a lot. More than I thought I would. More than I think I should. If I can be blunt, the question is wrong. It demonstrates a lack of understanding of virtual services. It shows that, as a pre-sales person I haven’t positioned virtual services correctly, it shows that the thoughts of the user are stuck in old ways of working, it demonstrates that we have a long way to go before general understanding of virtual services is common.

 

But Rick, how can you say that? It’s an ideal use case for virtual services, as all the functionality to enable keeping count is built-in to DevTest! This is true, and DevTest can also monitor live systems, it can perform software-defined routing, it can do load balancing, it can provide security against SQL injection, it can control IoT devices, and it can (probably – don’t try this one) perform DDoS attacks. But we would never do any of these things with it, because there are other products, better suited to perform all those functions.

 

If you ask me to keep count in a virtual service, three things will happen:

  1. I will tell you you’re wrong.
  2. I will argue against you.
  3. I will laugh at you.

If you still want me to tell you how to keep count in a virtual service, I will explain, and that is what this document is for.

 

Before I do, I need to provide some explanation as to why you’re wrong. Let’s use pieces of this diagram, which is something that I present when introducing the concepts of service virtualization. As you can see, it shows an application that we care about; in this situation, it’s “Order Management”, and many back-end connections that are removed by the implementation of virtual services, to allow us to test “Order Management” without having the constraints of mainframe scheduled access, system-of-record synchronized data, ERP provisioning and lack of availability of third-party links.

It all looks good, and we can provide service responses to any requests made by Order Management. These responses should have certain characteristics:

  • They should be data-aware.
  • They should be context-aware.
  • They should be date-aware.

The three different kinds of awareness listed above are technical inferences that come from the request and matched response. They are completely portable, they are immediately re-creatable, they are always reliable, thy are unconstrained. There is no business knowledge required to generate, manage, maintain and create a virtual service with these characteristics.

 

It’s only a short step from those three kinds of awareness to keeping count, surely?

 

Actually, no! As soon as we try to do any more in a virtual service than what’s listed above, we need to be aware of how the back-ends work, and none of us signed up for being aware of how back-ends work! Keeping count is one of the simplest business functions, but it is a business function nevertheless.

 

Why should something this trivial be such a problem? Surely, Rick, virtual services can’t be so restrictive that we can’t do this?

 

Of course we CAN do this. It’s just that we SHOULDN’T! Think about keeping count. Let’s continue with “Order Management”. What might need keeping count? Stock quantity and order value are the obvious ones. Ok, let’s think these through:

 

Stock quantity:

NFT

We have 1,000,000 doo-widgets in the inventory management system. This is in our ERP system, so we’ve virtualized that constraint away.

My test orders 1 doo-widget.

I want to performance test my order management system. My performance test wants to run for 8 hours, at 100 orders per second, to replicate what might happen on Black Friday.

 

Is it important to my test that I have 999,999 doo-widgets available after I’ve ordered one? It is important to my test that I have 500,000 doo-widgets left after I’ve ordered 500,000 of them? Is it important to my test that it falls over in a heap after 3 hours because I hit an inventory level of 0? If the answer to all of these questions is “no”, then it doesn’t matter what quantity is returned, and you don’t need to keep count in your virtual service; in fact, keeping count is an unnecessary overhead to responding as fast as you want. If the answer to any of these questions is “yes”, then you’re performing functional testing, not performance testing.

 

Ok, ok, so you’re doing functional testing, not performance testing. Let’s reset and start again.

 

FT

We have 1,000,000 doo-widgets in the inventory management system. This is in our ERP system, so we’ve virtualized that constraint away.

My test orders 1 doo-widget.

I want to functionally test my order management system. I want to make sure I can order items. I want to make sure I get a nice message when I’m out of stock. I want to make sure I can only request a valid number of doo-widgets.

 

How long will it take me to run through 1,000,000 inventory items, so I get to 0 and can perform my negative test? Too long. Ok, let’s reset the data to 10 doo-widgets and start again.

 

I run through my test 9 times, and it works each time. I run through it a 10th time, and I expect something different to happen. But this is a service, and services are shared, so there’s no guarantee that I’m the only person requesting inventory. In which case, I can’t be sure that I can run my test 9 times with the same expected result. In any case, why run through the same test 9 times, when this doesn’t hit a boundary condition? It would be better to test one positive condition, the negative condition, one timeout, one instance of each back-end error, one malformed response, each different response linked to a different inventory line item. By changing the way I test to do this, I increase my test coverage and decrease the amount of over-testing I do, whilst making my virtual service accurate, valuable, maintainable and throwaway.

 

But, I hear you argue, my front-end application keeps inventory count, and checks inventory values! I need to keep count. My response to this is that you need to log a defect against your front-end application, as it should never be keeping its own count. You need one master record, and this should be at the back-end.

 

Ok, the doo-widget is a simple counting function. What about order values? Surely these are more important, more complicated, more valuable and a better candidate for keeping count?

 

No, they aren’t. The exact same argument can be used for order values as is used for inventory tracking. If we aren’t hitting a boundary condition, why waste testing time by repeating the same test with the same expected result, rather than expanding our test with boundary and negative conditions. Moving to the DevTest demo application, if I’ve given you a demonstration of DevTest, I will have shown you that the current balance after an initial $100 deposit is $1100. I will also have told you that, on replay, this is our expected result. If the replay shows a different value, the application under test is doing something wrong.

 

Have I convinced you yet that keeping count in a virtual service is wrong? If not, please respond to this so we can discuss your specific use case. There is a chance that you might have a use case where you need to share data between different invocations of a service, and I’ve seen valid examples of these in the past, but I always treat these as exceptions to good service virtualization practice.

 

Now you know it’s wrong, and you know why it’s wrong, it’s time to explain how we enable you to do the wrong thing.

There are various mechanisms that we can use in DevTest to do this, depending on the scope required. The scope can be:

  • Contained within a single invocation of a running virtual service.
  • Shared within multiple invocations of a virtual service within one virtual service environment.
  • Shared within multiple invocations of multiple virtual services within one virtual service environment.
  • Shared within multiple invocations of multiple virtual services running in multiple virtual service environments.

Where might each of these be created?

Scope

Conversational Service Image

Service Model and/or Image

Shared Model Map

Persistent Model Map

Self-contained

 

 

 

 

Shared amongst invocations of a single virtual service

 

 

 

 

Shared amongst multiple virtual services in one VSE

 

 

 

 

Shared amongst multiple virtual services in multiple VSEs

 

 

 

 

We need some examples of these. Before we do, there are some restrictions we need to be aware of:

Anything in a block of XML is text. There is no concept of variable typing inside a message. But if we add two pieces of text together, we get concatenated values (“2” + “2” = “22”). So, if we’re using XML, we might need to convert from strings to numbers so we can perform simple mathematical operations, and back again to make them valid in a message. Some of the techniques listed below will contain this restriction, but DevTest is, wherever possible, type-less, so direct in-line property manipulation will work as expected (2 + 2 = 4).

 

As we progress towards the right in the above table, things get more flexible and more complicated (such as needing to be aware of type, as explained above). We can use a persistent model map for a self-contained service, it is usually easier & more intuitive to use in-line functionality.

 

We will be making extensive use of DevTest properties in the next few sections. If you need an introduction to these, you might like to refer to the DevTest Scripting Guide. I will try to explain what I’m using, but the scope of this document doesn’t extend to a scripting primer.

 

There are various places in a virtual service where properties can be defined and manipulated. These include:

  • Datasets applied to steps in service models
  • Script steps in service models
  • Scriptable data protocol handlers in service models
  • Routing steps in service models
  • Match scripts in service images
  • Inline scripts in service image data values

All of these are explained in the DevTest Scripting Guide. The following sections use a couple of them.

 

Self-contained

There is a pre-requisite for a conversational service image. It needs to have a token or session marker, sent in a response, which is re-used in each subsequent request. If your server application doesn’t have this, you will have troubles generating a conversational service image, and you will need to find a different mechanism for keeping count.

A Self-contained counter should allow addition and subtraction for one client, without altering the value for any other client. An example of this kind of virtual service is included in your installation of DevTest, in the “examples” project; the “kioskV5-dynamic” service image. Let’s break it down to see how it works:

The first thing is the session marker. This is in the response to login, and is represented in the service image as {{lisa.vse.session.key}}. This is a special property generated by DevTest, providing a unique and unchanging number to use throughout the conversation.

What about a counter for the current balance? The initial balance is set in the metadata to the “login” request. The login has no balance in the response, so the current balance is calculated in metadata:

The balance needs to be set here, so that it’s initialized to a value. The balance is then maintained in each response. It is possible that your transport protocol is sensitive to custom metadata keys, in which case you’ll need to set the property in a match script instead of the transaction metadata. A match script might contain:

testExec.setStateValue(“currentBalance”, 10000);

return defaultMatcher.matches();

DepositMoney response contains this:

so it is adding request_amount to currentBalance, and storing the result in currentBalance. Note the syntax for this kind of operation is {{resultProperty=sourceProperty1+sourceProperty2;}}. Don’t forget that semi-colon, in any of these property manipulations.

WithdrawMoney has no balance in the response, so the current balance is calculated in metadata:

This is subtracting request_amount from currentBalance, and storing the result in currentBalance.

GetAccount reports the current value:

By modifying each response in this way, the current balance is local to the session in which it’s being manipulated, so there is no pollution of data and the response values are always direct calculations. This means that no asynchronous processes can update the balance, such as interest being added by the server, or account debits taken by the server.

 

Shared between invocations:

If our virtual service is stateless, the same balance manipulation can be used as in the previous example. However, every time someone logs into the virtual service, as any user, the current balance is overwritten, and every transaction overwrites the balance, regardless of the user or account being used. The data is completely polluted, and the tester can never rely on an expected response value. At the same time, the current balance isn’t exposed to other virtual services, so the same property name can be used by different virtual services concurrently with no danger of inter-service interaction.

 

Shared amongst virtual services inside a single VSE:

Now we get to some of the more sophisticated property manipulations in DevTest, starting with the sharedModelMap. A strange name, it is a Java Map, designed for service models, with data shared inside a Java virtual machine (JVM). All virtual services running in a single VSE share the same JVM, so we have control over what and how we use these properties.

All values stored into a shared model map are strings, so any manipulation of a value will need to convert the string value to a number, and convert back to a string before storing in the map. This will mean that it’ll be very confusing to add inside property blocks in a service image, so we want to find other ways of adding scripting functionality for maximum flexibility, but with ease-of-use (or even hidden) technicalities.

My currentBalance value is a number, so we need to convert between strings and the number format. In the case of integers, we can do the following:

int currentBalanceInt = Integer.parseInt(currentBalanceString);

String currentBalanceString = String.valueOf(currentBalanceInt);

However, the numbers in the demo application are decimals, and there’s a specific support in Java for decimal numbers:

BigDecimal currentBalance=new BigDecimal("10000.00");

String currentBalanceString = currentBalance.toString();

BigDecimal currentBalance = new BigDecimal(currentBalanceString);

currentBalance = currentBalance.add(amount);

currentBalance=currentBalance.subtract(amount);

There are many things that can be done with a sharedModelMap, and they are described in the DevTest Scripting Guide. We will mainly be using the get and set methods.

com.itko.lisa.vse.sharedModelMap.put(namespace, key, value);

com.itko.lisa.vse.sharedModelMap.get(namespace, key);

The “namespace” argument is a way of separating the same variable against different criteria. This provides us extra flexibility. For example, I might use:

com.itko.lisa.vse.sharedModelMap.put(accountIDString, “currentBalance”, currentBalanceString);

String currentBalanceString = com.itko.lisa.vse.sharedModelMap.get(accountIDString, “currentBalance”);

With some careful use of type conversion, I can do the same things to “currentBalance” in my response as in the previous examples. In addition, I have the ability to get and set the currentBalance for the specific account numbers, storing and manipulating the balance of every account without polluting the balance of any other account. Each balance is also shared between any virtual services running in the same VSE, so we’ve enabled data sharing inside our container as well as keeping count.

If we’re going to try to use shared model maps in-line, it’s going to get very complicated. It will be more readable (and maintainable) if we put them into scripts.

Because we’re setting properties in a script, away from the actual message, we will need to change our data values to properties in the service image. {{currentBalance}} would be used wherever the current balance is required.

Firstly, a response-side scriptable data protocol handler, to link login names and checking accounts:

%beanshell%

opName = incomingRequest.getOperation();

_logger.debug("\n\n\n\n\nResponse DPH: Operation = {}", opName);

if("listUsers".equals(opName)) {

    _logger.debug("setting account numbers for login names ...");

    myMessage = lisa_vse_response.getBodyText();

    String[] segment = myMessage.split("<return>");

    for(i=1;i<segment.length; i++) {

        if(segment[i].contains("CHECKING")) {

            loginStartPos = segment[i].indexOf("<login>") + 7;

            loginEndPos = segment[i].indexOf("</login>");

            idStartPos = segment[i].indexOf("<id>") + 4;

            idEndPos = segment[i].indexOf("</id>");

            String login = segment[i].substring(loginStartPos, loginEndPos);

            String id = segment[i].substring(idStartPos, idEndPos);

            _logger.debug("Response DPH: login: {}, id: {}", login, id);

            com.itko.lisa.vse.SharedModelMap.put("account", login, id);

            _logger.debug("Response DPH: Check - Retrieved ID: {}", com.itko.lisa.vse.SharedModelMap.get("account", login));

        }

    }

}

_logger.debug("\n\n\n");

A quick note here – we have control of the message, and what to do inside the message. I could have chosen to convert the response message to an XML Document, and performed XML parsing on it. However, it occurred to me that, although the message format resembles XML, it can contain any content, so it’s just as valid to perform string manipulation as DOM traversal, and it’s easier for me to understand, so I’ll do it that way!

Now for match scripts:

Login:

//getNewToken match script

if (!incomingRequest.getOperation().equals("getNewToken"))

  return defaultMatcher.matches();

import com.itko.util.ParameterList;

import java.math.BigDecimal;

ParameterList args = incomingRequest.getArguments();

_logger.debug("\n\n\n\n\nMatch script getNewToken: Argument list:\n{}", args);

String login = args.getParameterValue("username");

if(login!=null) {

    BigDecimal currentBalance=new BigDecimal("10000.00");

    _logger.debug("Match script getNewToken: Allocating user {} an amount of {}", login, currentBalance);

    String accountID = com.itko.lisa.vse.SharedModelMap.get("account", login);

    _logger.debug("Match script getNewToken: ... to accountID: {}", accountID);

    com.itko.lisa.vse.SharedModelMap.put(accountID, "currentBalance", currentBalance.toString());

}

return defaultMatcher.matches();

Deposit:

// depositMoney match script

if (!incomingRequest.getOperation().equals("depositMoney"))

  return defaultMatcher.matches();

import com.itko.util.ParameterList;

import java.math.BigDecimal;

ParameterList args = incomingRequest.getArguments();

String amountString = args.getParameterValue("amount");

if(amountString != null) {

    _logger.debug("depositMoney match script: Amount = {}", amountString);

    BigDecimal amount = new BigDecimal(amountString);

    String accountID = args.getParameterValue("accountID");

    BigDecimal currentBalance = new BigDecimal(com.itko.lisa.vse.SharedModelMap.get(accountID, "currentBalance"));

    currentBalance = currentBalance.add(amount);

    com.itko.lisa.vse.SharedModelMap.put(accountID, "currentBalance", currentBalance.toString());

    testExec.setStateValue("currentBalance", currentBalance.toString());

}

return defaultMatcher.matches();

Get balance:

// getAccount match script

if (!incomingRequest.getOperation().equals("getAccount"))

  return defaultMatcher.matches();

import com.itko.util.ParameterList;

import java.math.BigDecimal;

ParameterList args = incomingRequest.getArguments();

String accountID = args.getParameterValue("accountID");

BigDecimal currentBalance = new BigDecimal(com.itko.lisa.vse.SharedModelMap.get(accountID, "currentBalance"));

testExec.setStateValue("currentBalance", currentBalance.toString());

return defaultMatcher.matches();

Withdraw:

// withdrawMoney match script

if (!incomingRequest.getOperation().equals("withdrawMoney"))

  return defaultMatcher.matches();

import com.itko.util.ParameterList;

import java.math.BigDecimal;

ParameterList args = incomingRequest.getArguments();

String amountString = args.getParameterValue("amount");

if(amountString != null) {

    BigDecimal amount = new BigDecimal(amountString);

    String accountID = args.getParameterValue("accountID");

    BigDecimal currentBalance = new BigDecimal(com.itko.lisa.vse.SharedModelMap.get(accountID, "currentBalance"));

    currentBalance=currentBalance.subtract(amount);

    com.itko.lisa.vse.SharedModelMap.put(accountID, "currentBalance", currentBalance.toString());

    testExec.setStateValue("currentBalance", currentBalance.toString());

    }

return defaultMatcher.matches();

I have removed all the mathematical functions from in-line properties in my service image, and I simply use {{currentBalance}} in my responses.

 

Sharing amongst multiple VSEs:

A Persistent Model Map works in the same way as a Shared Model Map, but its scope is greater. It stores data in the DevTest database to which the Registry is connected. It, therefore, can share data between all components running against that registry.

 

The getMapValue and putMapValue APIs in a Persistent Model Map work the same way as the get & put APIs in the Shared Model Map, the difference being that the values are persisted across reboots, across multiple VSEs, across multiple tests, for as long as needed (30 days is the default elapsed time before deleting them).

 

Remember, the Persistent Model Map permits values to be stored and retrieved, so you could use a test running in CVS to add interest to various accounts, take asynchronous debits from accounts, make the current balance invalid, and any other things that would make your testing impossible to manage deterministically.

 

I hope you can use my examples here to make your own virtual services keep count and share data. I hope MORE that you can look at my examples and decide that you would prefer your virtual services to add maximum benefit to the removal of constraints in test by not implementing storage and sharing of data, so you’re able to discard them at will, telling your tester what the expected result should be for any combination of input parameters.

Outcomes