Hero Background

Power Your Software Testing with AI Agents and Cloud

The Native AI-Agentic Cloud Platform to Supercharge Quality Engineering. Test Intelligently and Ship Faster.

Thought Leadership

Aggregate Model-Based Testing With Action-State Testing

Aggregate model-based testing merges stateless and stateful models. Action-state testing builds the model, covers requirements, and reveals missing actions.

Last Updated on:

Aggregate model-based testing covers stateless, stateful, and mixed test steps with one modeling technique instead of forcing a choice between two models. Action-state testing is currently the only aggregate method, introduced in the book Paradigm Shift in Software Testing and implemented in the Harmony tool, where every step pairs a user action, a system response, and the inner test state. This guide covers where you can use action-state testing and whether an LLM can build or traverse an action-state model.

Key Takeaways

  • Action-state testing is the only aggregate model-based testing method, and it covers stateless, stateful, and mixed test steps through modeling rather than coding.
  • An action-state step is a triple of a user action, a system response, and the inner test state, written as plain text instead of code.
  • Action-state models use implementation-independent wording, so a team can build and validate the model against the specification before the code exists.
  • A new state belongs in an action-state model when the same action makes the system calculate its response in a different way.
  • Generating a statechart from an action-state model exposes missing actions, which expanded the shopping cart example from 5 requirement-covering test cases to 13.
  • An LLM traversal of a graph model reached 96.3% edge coverage in a 2026 evaluation while the GraphWalker generator reached 100%, so a deterministic generator should stay the coverage authority.

Where can we use Action-state testing?

Action-state testing can be used for both stateful and stateless cases or even a mixed solution is possible if a long test case consists of inner states at some test steps, but at some other steps don't.

The 'action-state' model is textual, containing 'action-state' steps. Test cases are generated from the action-state model. An action-state step is a triple containing

  • a (user) action,
  • a (system) response,
  • the inner (test) state.

Actions and responses must be implementation-independent and as abstract as possible. Action is described at a high level so that humans can understand. For example, an action can be as:

add items so that price remains below 10This is more abstract than adding two more concrete actions:

  • add item for 7
  • add item for 2

Even more concrete actions would be:

  • click on the '+' icon next to Pizza Mexicana
  • click on the '+' icon next to Coke

However, these are implementation-dependent actions and can be created when the implementation is ready. Making an implementation-independent model is a huge advantage as it can be done before coding and the team can validate it against the specification detecting almost all the problems in it.

Modeling is easy: a subsequent step (step 2) is executed immediately after a step (step 1) if step 2 is indented to the right:

step 1

step 2

If two steps' indentation is the same then they will be executed in different test cases:

step A

step B.

step A may contain an action and a response (stateless step):

action A => response A

or may contain a triple involving the inner state:

action A => response A STATE state for A

Models can be easily mapped with the requirements:

R1 my requirement A:

action A => response A STATE state for A

One model can embed other models. In this way, very complex systems can also be modeled.

You can easily build a model related to a feature or user story. From this model, the tool can generate the statechart and the test cases immediately. The statechart is another model representation, see later. By applying statecharts, you can easily detect missing actions and states. On the other hand, a statechart contains less information. The information on whether a transition is traversed more times is missing. For example, entering the wrong PIN for the first and second time is modeled in this way:

Add wrong PIN => wrong PIN added STATE waiting for PIN

Add wrong PIN => wrong PIN added STATE waiting for PIN

The related statecharts are the same independently of only once or twice the wrong PIN was added. Action-state models contain more information than statecharts and this is the reason why this technique doesn't need guard conditions and consequently coding.

However, to avoid coding the models are a little bit larger.

You can also set an appropriate test selection criterion. A tool can offer the missing action-state steps to satisfy the criterion. There may be steps to be offered that are invalid. Fortunately, you can easily recognize an invalid step and it can be rejected. Accepting or rejecting an offered step is the replacement of adding guard conditions and some other coding. It's much easier and non-coder testers can also do it.

Let's consider the same requirements as in my previous blog:

how to find appropriate states

The most difficult part is how to find appropriate states. A reasonable solution is that if two actions are the same, but the system calculates the response in different ways, then we arrived at different states. Here is an example. The initial state is 'No discount, no bike'. From here we can add a car for which we go back to the initial state as no discount happens. Adding a second car, the calculation is the same as a car added to the cart. However, when ad the third car, the calculation will be different as a bike is also added for free. Thus we arrived at a new state 'Discount, bike added '. If we start with adding a bike and then adding three cars in a row, then there is another different calculation as the bike becomes free. Thus this is a new state: 'Discount, bike converted '. It's obvious that different calculations can be tested as any may contain an error. In this way, you can select a minimum number of test states.

Adding a bike at the same level as the first car, we go to another state 'No discount bike included'. All these steps cover requirement R1. This means that covering a single requirement with a single test is usually not enough. Here is the first version of the model:

INITIAL STATE No discount, no bike 
R1 The customer can add cars or bikes one by one: 
    add car => one car STATE No discount, no bike
        add car => two cars STATE No discount, no bike 
    add bike => bike added STATE No discount bike included

and the related statechart:

related statechart

You can see that the statechart doesn't show that we added two cars. For covering requirement R2, we should delete a car/bike that should be a child step of a parent step where at least one car/bike is available:

INITIAL STATE No discount, no bike 
R1 The customer can add cars or bikes one by one: 
    add car => one car STATE No discount, no bike
        add car => two cars STATE No discount, no bike 
          R2 The customer can remove cars or bikes one by one:   
            delete car => one car STATE No discount, no bike     
    add bike => one bike STATE No discount bike included 
      R2 The customer can remove cars or bikes one by one:  
        delete bike => cart empty STATE No discount, no bike

To cover R3a, we should add two cars to the single bike:

add bike => one bike STATE No discount bike included 
      R3a discount for EUR 700 when bike converted free:
        add two cars => two cars and a bike STATE Discount, bike converted

R3b can be covered if we add a third car:

add car => two cars STATE No discount, no bike 
          R3b For discount a bike is added when only cars are in the cart:
            add car => three cars and a bike STATE Discount, bike added

Now it's reasonable to continue with R4. To cover it we remove the third car:

add car => three cars and a bike STATE Discount, bike added 
              R4 going below the discount limit it is withdrawn:
                delete car => two cars STATE No discount, no bike

Considering R3c, we should add the deleted car again:

delete car => two cars STATE No discount, no bike 
                  R3c reaching the limit again, previous discount back:
                    add car => three cars and a bike STATE Discount, bike added

Our last requirement is R3d. We cover it by deleting the second car, adding a bike, and adding the second car again:

delete car => two cars STATE No discount, no bike 
                  R3d reaching the limit again adding a bike before:
                    delete car => one car STATE No discount, no bike 
                      add bike => one car and a bike STATE No discount bike included 
                        add car => two cars and a bike STATE Discount, bike converted

The whole model is the following:

INITIAL STATE No discount, no bike 
R1 The customer can add cars or bikes one by one: (1)
    add car => one car STATE No discount, no bike
        add car => two cars STATE No discount, no bike 
(2)       R3b For discount a bike is added when only cars are in the cart:
            add car => three cars and a bike STATE Discount, bike added 
(3)           R4 going below the discount limit it is withdrawn:
                delete car => two cars STATE No discount, no bike 
(4)               R3c reaching the limit again, previous discount back:
                    add car => three cars and a bike STATE Discount, bike added
(5)               R3d reaching the limit again adding a bike before:
                    delete car => one car STATE No discount, no bike 
                      add bike => one car and a bike STATE No discount bike included 
                        add car => two cars and a bike STATE Discount, bike converted
(6)       R2 The customer can remove cars or bikes one by one:     
            delete car => one car STATE No discount, no bike       
    add bike => one bike STATE No discount bike included 
(7)   R3a discount for EUR 700 when bike converted free:
        add two cars => two cars and a bike STATE Discount, bike converted   
(6)   R2 The customer can remove cars or bikes one by one:  
        delete bike => cart empty STATE No discount, no bike

We covered the requirements with only five test cases (the ones (1) - (5)). Are we ready? Let's see. The related statechart is here:

related statechart

It's a great help as missing actions can be detected easily. For example, from the state 'Discount, bike added' we can delete or add a bike. Considering 'No discount bike included', there is a missing edge going to itself, when deleting a bike from two. We can also add bike going to itself and going to 'Discount, bike converted'. Finally, considering 'Discount, bike converted', there are several missing actions, i.e., adding/deleting a car and adding/deleting a bike to itself, delete bike going to 'No discount no bike' and delete car/bike going to 'No discount bike included'.

Traversing each valid action/edge is the minimum test selection criterion, thus covering only the requirements is clearly insufficient. Involving the missing actions, we get the following improved model:

INITIAL STATE No discount, no bike 
R1 The customer can add cars or bikes one by one: 
    add car => one car STATE No discount, no bike
        add car => two cars STATE No discount, no bike 
          R3b For discount a bike is added when only cars are in the cart:
            add car => three cars and a bike STATE Discount, bike added 
              add bike => three cars and two bikes STATE Discount, bike added 
              delete bike => three cars STATE No discount, no bike  
              R4 going below the discount limit it is withdrawn:
                delete car => two cars STATE No discount, no bike 
                  R3c reaching the limit again, previous discount back:
                    add car => three cars and a bike STATE Discount, bike added
                  R3d reaching the limit again adding a bike before:
                    delete car => one car STATE No discount, no bike 
                      add bike => one car and a bike STATE No discount bike included 
                        add car => two cars and a bike STATE Discount, bike converted  
          R2 The customer can remove cars or bikes one by one:     
            delete car => one car STATE No discount, no bike       
    add bike => one bike STATE No discount bike included 
      add bike => two bikes STATE No discount bike included 
        add five bikes => seven bikes STATE Discount, bike converted 
          add bike => eight bikes STATE Discount, bike converted 
            delete bike => seven bikes STATE Discount, bike converted 
          delete bike => six bikes STATE No discount bike included 
        delete bike => one bike STATE No discount bike included  
      R3a discount for EUR 700 when bike converted free:
        add two cars => two cars and a bike STATE Discount, bike converted  
          delete car => one car and a bike STATE No discount bike included 
          add car => three cars and a bike STATE Discount, bike converted  
            delete car => two cars and a bike STATE Discount, bike converted 
          delete bike => two cars STATE No discount, no bike 
          add bike => two cars and two bikes STATE Discount, bike converted 
      R2 The customer can remove cars or bikes one by one:  
        delete bike => cart empty STATE No discount, no bike     
      delete bike => cart empty STATE No discount, no bike

The number of test cases increased from 5 to 13. The new statechart is the following:

new statechart

Now all the actions are involved. We have seven requirements and we carefully covered by 13 test cases. You may think 13 tests are too many, but it depends on the software risk analysis. If the risk is high, you should test it carefully, otherwise tricky bugs remain undetected. You cannot merge cars and bikes as vehicles as they behave differently, only bikes are added freely or converted for free, and cars are not.

Considering the statechart, if you simply traverse it, some invalid tests are generated. For example, adding a bike from the initial state, then adding five bikes we arrive at 'Discount, bike converted'. However, having only six bikes we have no discount, a contradiction. The state action-state model consists of only valid steps.

As mentioned, by applying a stronger test selection criterion, new steps are offered. For example, when applying all-transition-pairs criterion the following step is offered:

test selection criterion

This is because the edge/action pair (add car, add bike), where add car is the action starts from and arrives at the initial state hasn't been covered. If the risk is very high you should add these steps as well.

Abstract steps become executable tests through a mapping layer. Each action, such as "add car", maps to one function or method in the page object model of the automation code, and each response maps to one assertion. The model stays implementation-independent, so a UI change edits the mapped function and leaves the model untouched. The same action-state model can therefore drive a manual run, a browser test, and API testing without being rewritten.

Keep the model in version control next to the test code and regenerate the test cases in your pipeline. A requirement change then edits one action-state step, and regeneration only touches the test cases that pass through that step. Reviewers read the model diff in the pull request instead of rereading every generated test case, and requirement labels such as R3b stay attached to the tests that cover them.

Test across 3000+ browser and OS environments with TestMu AI

Key Takeaway: Action-state testing fits stateless, stateful, and mixed systems, and the statechart generated from an action-state model reveals the missing actions that requirement coverage alone leaves untested.

Can an LLM build or traverse an action-state model?

An LLM can traverse an existing action-state model and shorten the generated paths, but it cannot be trusted to author the model or to certify coverage. The two jobs are different. Building the model means reading the requirements and deciding where a new state begins. Traversing the model is mechanical path selection over a graph.

An empirical evaluation published in August 2026 measured that split. The authors ran five models, GPT-5.1, GPT-5.2, Claude Opus 4.5, Claude Sonnet 4.5 and Gemini 2.5 Pro, against GraphWalker, an open source MIT-licensed path generator, on four graph models, from a 10 vertex traffic light controller to a 129 vertex web application. Their LLM4MBT pipeline averaged 100% vertex coverage and 96.3% edge coverage in about 300 test steps. GraphWalker's quick random generator reached 100% edge coverage in 524 steps. The paths were shorter, and a small share of edges went untraversed.

For action-state testing the untraversed edges matter more than the step count. The statechart is what exposes missing actions, such as the delete bike edge that returns to the same state, so a traversal that stops short of full edge coverage removes the check this method depends on. The same paper reports that one prompt can produce different paths across runs, and that the five models were not equally reliable, one failing on the RISC-V graph. Keep a deterministic generator as the coverage authority. GraphWalker accepts edge coverage, vertex coverage and requirement coverage as stop conditions and replays a run from a seed, so the coverage number stays reproducible.

The useful place for an assistant is the offered-step loop above. You set the test selection criterion, the tool offers the missing steps, and a model can draft the action and response wording in implementation-independent language. You still accept or reject each offered step, and that judgement is the part no generator replaces.

Key Takeaway: An LLM can traverse an existing action-state model and produce shorter paths, but authoring the model and certifying edge coverage still need a human reviewer and a deterministic generator such as GraphWalker.

Conclusion

Action-state testing is an aggregate MBT method. This is the only one that requires no coding at all, just modeling. The model can be converted to a statechart, by which the original model can be improved and made complete. It can be widely used, and tricky bugs can be detected. From the models, abstract test cases are generated that can be manually executed. The model consists of implementation-independent steps, thus it's a true shift left testing method for MBT.

Author

...

Istvan Forgacs

Blogs: 8

  • Twitter
  • Linkedin

István Forgács, PhD is a software testing and test design automation expert with 30+ years of experience in test automation, model-based testing, and agile testing methodologies. He is the CEO and co-founder of 4Test-Plus and the creator of Harmony, a two-phase model-based test design automation tool focused on improving test maintainability and reducing testing costs. István has led EU- and Hungary-funded research projects, headed software testing groups, and researched core algorithms for testing tools and analyzers. He is the co-author of four books, including Modern Software Testing Techniques and Agile Testing Foundations, and actively contributes to advancing test design methods such as action state testing and general predicate testing through education, tools, and community platforms.

Add to Google preferred sources

Summarise with AI

Copied to Clipboard!
...

3000+ Browsers. One Platform.

See exactly how your site performs everywhere.

Try it free
...

Write Tests in Plain English with KaneAI

Create, debug, and evolve tests using natural language.

Try for free

Aggregate Model-Based Testing FAQs

Did you find this page helpful?

More Related Blogs

TestMu AI forEnterprise

Get access to solutions built on Enterprise
grade security, privacy, & compliance

  • Advanced access controls
  • Advanced data retention rules
  • Advanced Local Testing
  • Premium Support options
  • Early access to beta features
  • Private Slack Channel
  • Unlimited Manual Accessibility DevTools Tests