pymodel model-based testing in pythonstaff.washington.edu/jon/pymodel/talks/pymodel-nwpd10.pdf ·...

32
PyModel Model-based testing in Python Jon Jacky University of Washington [email protected] http://staff.washington.edu/jon/pymodel/www/ Jon Jacky Model-based testing in Python

Upload: others

Post on 05-Jul-2020

20 views

Category:

Documents


0 download

TRANSCRIPT

PyModelModel-based testing in Python

Jon Jacky

University of [email protected]

http://staff.washington.edu/jon/pymodel/www/

Jon Jacky Model-based testing in Python

Model-based testing

Unit testing: code each test case, including an assertion thatchecks whether the test passed

Model-based testing: code a model that generates as many testcases as desired, and also acts as the oracle that checks whetherany case passed

A model-based testing project is a programming project!

In PyModel the models are coded in Python. It is convenient whenthe implementation under test is also in Python (but this is notrequired).

Jon Jacky Model-based testing in Python

Model-based testing

What problem does model-based testing solve?

Testing behavior: ongoing activities that may exhibithistory-dependence and nondeterminism.

Many variations (data values, interleavings, etc.) should be testedfor each scenario (or use case).

So many test cases are needed that it is not feasible to code themall by hand.

Examples: communication protocols, web applications, controlsystems, user interfaces, ...

Jon Jacky Model-based testing in Python

Behavior

We need to test behavior: ongoing activities that may exhibithistory dependence and nondeterminism.

We represent behavior with traces: sequences of actions witharguments. Specify a system by describing which traces areallowed, and which are forbidden.

Example: alternating bit protocol

Allowed

Send(0)

Ack(0)

Send(1)

Ack(1)

Allowed

Send(1)

Send(1)

Ack(1)

Send(0)

Ack(1)

Ack(1)

Send(0)

Ack(0)

Allowed

Send(1)

Send(1)

Ack(1)

Send(1)

Ack(1)

Send(1)

Allowed

Send(1)

Ack(1)

Send(1)

Ack(1)

Ack(1)

Send(0)

Ack(0)

Forbidden

Send(0)

Ack(0)

Send(0)

Ack(0)

Forbidden

Send(0)

Ack(1)

Send(1)

Ack(1)

Jon Jacky Model-based testing in Python

Finite State Machines

Finite State Machines (FSMs) can represent finite behaviors. Everypath through the graph represents an allowed trace.

0

Ack(1)Send(1)

1Send(0)

2

Ack(0)

3Send(1)

4

Ack(1)

Send(0)

Ack(0)

Ack(1)Send(0)

Ack(1)Ack(0)Send(1)

Allowed

Send(0)

Ack(0)

Send(1)

Ack(1)

Allowed

Send(1)

Send(1)

Ack(1)

Send(0)

Ack(1)

Ack(1)

Send(0)

Ack(0)

Allowed

Send(1)

Send(1)

Ack(1)

Send(1)

Ack(1)

Send(1)

Allowed

Send(1)

Ack(1)

Send(1)

Ack(1)

Ack(1)

Send(0)

Ack(0)

Forbidden

Send(0)

Ack(0)

Send(0)

Ack(0)

Forbidden

Send(0)

Ack(1)

Send(1)

Ack(1)

Jon Jacky Model-based testing in Python

Finite State Machines

FSMs are one kind of model in PyModel, coded as follows:

0

Ack(1)Send(1)

1Send(0)

2

Ack(0)

3Send(1)

4

Ack(1)

Send(0)

Ack(0)

Ack(1)Send(0)

Ack(1)Ack(0)Send(1)

graph = ((0, (Send, (1,), None), 0),

(0, (Ack, (1,), None), 0),

(0, (Send, (0,), None), 1),

(1, (Ack, (0,), None), 2),

... etc. ...

(4, (Send, (0,), None), 1))

The PyModel Graphics program pmg generates graphics from anFSM in this form.

Jon Jacky Model-based testing in Python

Generating tests

The PyModel Tester pmt generates traces from a model. Eachtrace describes a test run, including the expected test results.

Offline testing: pmt saves the traces in a test suite.

On-the-fly testing: pmt executes the traces as they are generated.

C:\Users\jon\Documents\mbt\samples\abp>pmt.py -n 10 ABP

Send(1,)

Send(1,)

Ack(1,)

Send(1,)

Ack(1,)

Send(0,)

Ack(1,)

Send(0,)

Ack(0,)

Ack(0,)

Finished at step 10, reached accepting state

Jon Jacky Model-based testing in Python

Model programs

Model programs are another kind of model in PyModel. They candescribe behaviors where the action arguments can have an“infinite” (very large) number of values.

A model program consists of state variables, action functions andenabling conditions.

stack = list() # State

def Push(x): # Push is always enabled

global stack

stack.insert(0,x)

def Pop(): # Pop requires an enabling condition

global stack

result = stack[0]

del stack[0]

return result

def PopEnabled(): # Pop is enabled when the stack is not empty

return stackJon Jacky Model-based testing in Python

Exploration

The PyModel Analyzer pma generates an FSM from a modelprogram by a process called Exploration.

00

1

Push(0)

2

Push(1)

0

1

Push(0)

2

Push(1)Pop(0)

15

Push(0)

16

Push(1)

0

1

Push(0)

2

Push(1)Pop(0)

15

Push(0)

16

Push(1)

Pop(1)

4

Push(1)

3

Push(0)

etc ...

Jon Jacky Model-based testing in Python

Exploration

The PyModel Analyzer pma generates an FSM from a modelprogram by a process called Exploration.

0

0

1

Push(0)

2

Push(1)

0

1

Push(0)

2

Push(1)Pop(0)

15

Push(0)

16

Push(1)

0

1

Push(0)

2

Push(1)Pop(0)

15

Push(0)

16

Push(1)

Pop(1)

4

Push(1)

3

Push(0)

etc ...

Jon Jacky Model-based testing in Python

Exploration

The PyModel Analyzer pma generates an FSM from a modelprogram by a process called Exploration.

00

1

Push(0)

2

Push(1)

0

1

Push(0)

2

Push(1)Pop(0)

15

Push(0)

16

Push(1)

0

1

Push(0)

2

Push(1)Pop(0)

15

Push(0)

16

Push(1)

Pop(1)

4

Push(1)

3

Push(0)

etc ...

Jon Jacky Model-based testing in Python

Exploration

The PyModel Analyzer pma generates an FSM from a modelprogram by a process called Exploration.

00

1

Push(0)

2

Push(1)

0

1

Push(0)

2

Push(1)Pop(0)

15

Push(0)

16

Push(1)

0

1

Push(0)

2

Push(1)Pop(0)

15

Push(0)

16

Push(1)

Pop(1)

4

Push(1)

3

Push(0)

etc ...

Jon Jacky Model-based testing in Python

Exploration

The PyModel Analyzer pma generates an FSM from a modelprogram by a process called Exploration.

00

1

Push(0)

2

Push(1)

0

1

Push(0)

2

Push(1)Pop(0)

15

Push(0)

16

Push(1)

0

1

Push(0)

2

Push(1)Pop(0)

15

Push(0)

16

Push(1)

Pop(1)

4

Push(1)

3

Push(0)

etc ...

Jon Jacky Model-based testing in Python

Exploration

We must limit exploration of infinite programs. Here we define afinite domain to limit the width of the graph, and a state filter tolimit its depth.

domains = { Push: {’x’:[0,1]} }

def StateFilter():

return len(stack) < 4

0

1

Push(0)

2

Push(1)Pop(0)

15

Push(0)

16

Push(1)

Pop(1)

4

Push(1)

3

Push(0)Pop(1)

9

Push(0)

10

Push(1)

Pop(0)

5

Push(0)

6

Push(1)Pop(0) Pop(1)Pop(0) Pop(1)

Pop(0)

25

Push(0)

26

Push(1)

Pop(1)

18

Push(1)

17

Push(0)Pop(1) Pop(0)Pop(0) Pop(1)

Jon Jacky Model-based testing in Python

Strategies

Test generation can select the next enabled action at random, oruse an optional strategy to select an action that increases coverageaccording to some measure.

> pmt.py Stack

Push(1,)

Push(2,)

Push(2,)

Push(1,)

Pop(), 1

Pop(), 2

Pop(), 2

Push(2,)

Push(1,)

Push(1,)

> pmt.py Stack

-g ActionNameCoverage

Push(1,)

Pop(), 1

Push(2,)

Pop(), 2

Push(1,)

Pop(), 1

Push(2,)

Pop(), 2

Push(1,)

Pop(), 1

> pmt.py Stack

-g StateCoverage

Push(1,)

Push(2,)

Push(2,)

Push(1,)

Push(1,)

Push(1,)

Push(2,)

Push(2,)

Push(1,)

Push(1,)

ActionNameCoverage and StateCoverage are included in PyModel.You can also code your own custom strategy.

Jon Jacky Model-based testing in Python

Composition

We need scenario control to limit test runs to scenarios of interest.

PyModel uses composition, a versatile technique combines two ormore models to form a new model, the product.

M1 ×M2 = P

Usually we combine a contract model program (with actionfunctions, etc.) with a scenario machine, an FSM.

Contract × Scenario = Product

Composition can also be used for validation, program structuring,etc. . . .

Jon Jacky Model-based testing in Python

Composition

Composition synchronizes shared actions.

0

2

Push(1)

1

Push(0)Pop(1)

4

Push(1)

3

Push(0)

Pop(0)

9

Push(0)

10

Push(1)Pop(0) Pop(1)Pop(1) Pop(0)

× 0

1

Push(1)Pop(1)

Push(1)

=

0

2

Push(1)Pop(1)

1

Push(1)Pop(1)

This usually has the effect of restricting behavior.

Jon Jacky Model-based testing in Python

Composition

Composition synchronizes shared actions.

0

2

Push(1)

1

Push(0)Pop(1)

4

Push(1)

3

Push(0)

Pop(0)

9

Push(0)

10

Push(1)Pop(0) Pop(1)Pop(1) Pop(0)

× 0

1

Push(1)Pop(1)

Push(1)

=

0

2

Push(1)Pop(1)

1

Push(1)Pop(1)

This usually has the effect of restricting behavior.

Jon Jacky Model-based testing in Python

Composition

Composition synchronizes shared actions.

0

2

Push(1)

1

Push(0)Pop(1)

4

Push(1)

3

Push(0)

Pop(0)

9

Push(0)

10

Push(1)Pop(0) Pop(1)Pop(1) Pop(0)

× 0

1

Push(1)Pop(1)

Push(1)

=

0

2

Push(1)Pop(1)

1

Push(1)Pop(1)

This usually has the effect of restricting behavior.

Jon Jacky Model-based testing in Python

Composition

Composition interleaves unshared actions.

0

1

PowerOn()PowerOff()

×0

1

IncrementSpeed()

2

IncrementSpeed()

IncrementSpeed()=

0

1

PowerOn()

2

IncrementSpeed()

PowerOff()

3

IncrementSpeed()

4

IncrementSpeed()

PowerOn()

IncrementSpeed()

5

PowerOn()

PowerOff()

IncrementSpeed()

IncrementSpeed()

PowerOff()

This usually has the effect of adding behavior.

Jon Jacky Model-based testing in Python

Composition

Composition interleaves unshared actions.

0

1

PowerOn()PowerOff()

×0

1

IncrementSpeed()

2

IncrementSpeed()

IncrementSpeed()

=

0

1

PowerOn()

2

IncrementSpeed()

PowerOff()

3

IncrementSpeed()

4

IncrementSpeed()

PowerOn()

IncrementSpeed()

5

PowerOn()

PowerOff()

IncrementSpeed()

IncrementSpeed()

PowerOff()

This usually has the effect of adding behavior.

Jon Jacky Model-based testing in Python

Composition

Composition interleaves unshared actions.

0

1

PowerOn()PowerOff()

×0

1

IncrementSpeed()

2

IncrementSpeed()

IncrementSpeed()=

0

1

PowerOn()

2

IncrementSpeed()

PowerOff()

3

IncrementSpeed()

4

IncrementSpeed()

PowerOn()

IncrementSpeed()

5

PowerOn()

PowerOff()

IncrementSpeed()

IncrementSpeed()

PowerOff()

This usually has the effect of adding behavior.

Jon Jacky Model-based testing in Python

Validation

Composition with a scenario can help validate a model program.

0

2

Push(1)

1

Push(0)Pop(1)

4

Push(1)

3

Push(0)

Pop(0)

9

Push(0)

10

Push(1)Pop(0) Pop(1)Pop(1) Pop(0)

× 0

1

Push(1)Pop(0)

= 0

1

Push(1)

The product shows whether the model program can execute thecomplete scenario. Does the product reach an accepting state?

Jon Jacky Model-based testing in Python

Validation

Composition with a scenario can help validate a model program.

0

2

Push(1)

1

Push(0)Pop(1)

4

Push(1)

3

Push(0)

Pop(0)

9

Push(0)

10

Push(1)Pop(0) Pop(1)Pop(1) Pop(0)

× 0

1

Push(1)Pop(0)

= 0

1

Push(1)

The product shows whether the model program can execute thecomplete scenario. Does the product reach an accepting state?

Jon Jacky Model-based testing in Python

Validation

Composition with a scenario can help validate a model program.

0

2

Push(1)

1

Push(0)Pop(1)

4

Push(1)

3

Push(0)

Pop(0)

9

Push(0)

10

Push(1)Pop(0) Pop(1)Pop(1) Pop(0)

× 0

1

Push(1)Pop(0)

= 0

1

Push(1)

The product shows whether the model program can execute thecomplete scenario. Does the product reach an accepting state?

Jon Jacky Model-based testing in Python

Scenario Control

In this example we compose the model program with a scenariomachine to eliminate redundant startup and shutdown paths.

0

1

ClientSocket()

2

ServerSocket()

27

3

9

ClientSocket()

11

29

28

ServerClose()

4

ServerSocket()

ServerClose()

5

ServerBind() ClientSocket()

ServerClose() 7

ClientSocket()

6

ServerListen()

ServerClose()

ServerBind()

ServerClose()8

ServerListen()

ServerClose()

10

ClientConnect()

ServerClose()

12

ServerAccept()

16

ServerSend(double("99.9"))

13

ServerCloseConnection()

14

ClientClose()

15

ClientSend()

17

ServerSend(double("100"))

18

ServerCloseConnection()

19

ClientReceive_Start()

ClientSend()

26

ServerClose()

20

ClientClose()

ServerCloseConnection()ServerCloseConnection()

ServerReceive()

22

ClientReceive_Start()

21

ServerCloseConnection()

ClientReceive_Finish(double("100"))

23

ServerClose()

24

ClientReceive_Start()

25

ClientReceive_Start() ClientReceive_Finish(double("100"))

ClientReceive_Finish(double("100"))

ClientClose()ClientSend() ServerClose()

30

ServerClose()

31

ClientReceive_Start()

ClientReceive_Finish(double("99.9"))

32

ClientReceive_Start()ClientReceive_Finish(double("99.9"))

ClientReceive_Finish(double("99.9"))

ServerClose() ClientSocket()

×

0

1

ServerSocket()

9

2

ServerBind()

3

ServerListen()

4

ClientSocket()

5

ClientConnect()

6

ServerAccept()

7

ClientClose()

8

ServerCloseConnection()

ServerClose()

=

0

1

ServerSocket()

13

2

ServerBind()

3

ServerListen()

4

ClientSocket()

5

ClientConnect()

6

ServerAccept()

7

ClientSend()

8

ClientClose()

9

ServerSend(double("99.9"))

10

ServerSend(double("100"))ServerReceive()

12

ServerCloseConnection()

ClientReceive() / double("99.9") ClientReceive() / double("100")

ServerClose()

Now the product will only generate interesting traces.

Jon Jacky Model-based testing in Python

Scenario Control

In this example we compose the model program with a scenariomachine to eliminate redundant startup and shutdown paths.

0

1

ClientSocket()

2

ServerSocket()

27

3

9

ClientSocket()

11

29

28

ServerClose()

4

ServerSocket()

ServerClose()

5

ServerBind() ClientSocket()

ServerClose() 7

ClientSocket()

6

ServerListen()

ServerClose()

ServerBind()

ServerClose()8

ServerListen()

ServerClose()

10

ClientConnect()

ServerClose()

12

ServerAccept()

16

ServerSend(double("99.9"))

13

ServerCloseConnection()

14

ClientClose()

15

ClientSend()

17

ServerSend(double("100"))

18

ServerCloseConnection()

19

ClientReceive_Start()

ClientSend()

26

ServerClose()

20

ClientClose()

ServerCloseConnection()ServerCloseConnection()

ServerReceive()

22

ClientReceive_Start()

21

ServerCloseConnection()

ClientReceive_Finish(double("100"))

23

ServerClose()

24

ClientReceive_Start()

25

ClientReceive_Start() ClientReceive_Finish(double("100"))

ClientReceive_Finish(double("100"))

ClientClose()ClientSend() ServerClose()

30

ServerClose()

31

ClientReceive_Start()

ClientReceive_Finish(double("99.9"))

32

ClientReceive_Start()ClientReceive_Finish(double("99.9"))

ClientReceive_Finish(double("99.9"))

ServerClose() ClientSocket()

×

0

1

ServerSocket()

9

2

ServerBind()

3

ServerListen()

4

ClientSocket()

5

ClientConnect()

6

ServerAccept()

7

ClientClose()

8

ServerCloseConnection()

ServerClose()

=

0

1

ServerSocket()

13

2

ServerBind()

3

ServerListen()

4

ClientSocket()

5

ClientConnect()

6

ServerAccept()

7

ClientSend()

8

ClientClose()

9

ServerSend(double("99.9"))

10

ServerSend(double("100"))ServerReceive()

12

ServerCloseConnection()

ClientReceive() / double("99.9") ClientReceive() / double("100")

ServerClose()

Now the product will only generate interesting traces.

Jon Jacky Model-based testing in Python

Scenario Control

In this example we compose the model program with a scenariomachine to eliminate redundant startup and shutdown paths.

0

1

ClientSocket()

2

ServerSocket()

27

3

9

ClientSocket()

11

29

28

ServerClose()

4

ServerSocket()

ServerClose()

5

ServerBind() ClientSocket()

ServerClose() 7

ClientSocket()

6

ServerListen()

ServerClose()

ServerBind()

ServerClose()8

ServerListen()

ServerClose()

10

ClientConnect()

ServerClose()

12

ServerAccept()

16

ServerSend(double("99.9"))

13

ServerCloseConnection()

14

ClientClose()

15

ClientSend()

17

ServerSend(double("100"))

18

ServerCloseConnection()

19

ClientReceive_Start()

ClientSend()

26

ServerClose()

20

ClientClose()

ServerCloseConnection()ServerCloseConnection()

ServerReceive()

22

ClientReceive_Start()

21

ServerCloseConnection()

ClientReceive_Finish(double("100"))

23

ServerClose()

24

ClientReceive_Start()

25

ClientReceive_Start() ClientReceive_Finish(double("100"))

ClientReceive_Finish(double("100"))

ClientClose()ClientSend() ServerClose()

30

ServerClose()

31

ClientReceive_Start()

ClientReceive_Finish(double("99.9"))

32

ClientReceive_Start()ClientReceive_Finish(double("99.9"))

ClientReceive_Finish(double("99.9"))

ServerClose() ClientSocket()

×

0

1

ServerSocket()

9

2

ServerBind()

3

ServerListen()

4

ClientSocket()

5

ClientConnect()

6

ServerAccept()

7

ClientClose()

8

ServerCloseConnection()

ServerClose()

=

0

1

ServerSocket()

13

2

ServerBind()

3

ServerListen()

4

ClientSocket()

5

ClientConnect()

6

ServerAccept()

7

ClientSend()

8

ClientClose()

9

ServerSend(double("99.9"))

10

ServerSend(double("100"))ServerReceive()

12

ServerCloseConnection()

ClientReceive() / double("99.9") ClientReceive() / double("100")

ServerClose()

Now the product will only generate interesting traces.

Jon Jacky Model-based testing in Python

Test Harness

Executing tests requires a harness (or adapter) to connect themodel to the implementation. In PyModel a test harness is called astepper. Its core TestAction function contains a branch for eachaction in the model.

def TestAction(aname, args, modelResult):

...

if aname == ’Initialize’:

session = dict() # clear out cookies/session IDs from previous session

elif aname == ’Login’:

user = users[args[0]]

...

password = passwords[user] if args[1] == ’Correct’ else wrongPassword

postArgs = urllib.urlencode({’username’:user, ’password’:password})

page = session[user].opener.open(webAppUrl).read() # GET login page

...

if result != modelResult:

return ’received Login %s, expected %s’ % (result, modelResult)

elif aname == ’Logout’:

...

Jon Jacky Model-based testing in Python

On-the-fly Testing

On-the-fly testing generates test cases as the test run executes. Itovercomes some disadvantages of offline test generation.

No FSM is generated, needn’t finitize

Test runs can be indefinitely long, nonrepeating

Especially

Handles nondeterminism — responds to the nondeterministicchoice that the implementation actually made!

This last requires an asynchronous stepper that distinguishesbetween controllable actions (functions the stepper can call) andobservable actions (events the stepper can detect).

Jon Jacky Model-based testing in Python

Implications

Model-based testing can encourage different approaches to testing.

Encourages on-the-fly testing — but test runs may not bereproducible.

Extends testing to noninvasive monitoring or run timeverification — if the harness supports observable actions, thetester can check log files or monitor network traffic forconformance violations.

Enables better integration of design analysis with testing —exploration is like model checking, can check for safety,liveness, and temporal properties.

A rational workflow might be to write the model before writing theimplementation, analyze and tweak the design, then implementand test.

Jon Jacky Model-based testing in Python

Download PyModel

PyModel is an open-source model-based testing framework.

http://staff.washington.edu/jon/pymodel/www/

Jon Jacky Model-based testing in Python