graphwalker
Описание
This is the repo for the Model-based testing tool GraphWalker
Языки
JavaScript
- Java
- ANTLR
- HTML
- CSS
This is a fork of https://github.com/GraphWalker/graphwalker-project
Original documentation on http://graphwalker.org/
Added features
Here is description of added features.
Split into multiple graphs with the same context
The prehistory of this feature is as follows - in the library itself,
it is already possible to split the test model into separate files,
linking them together using the
mechanism of steps.
More information about this feature can be found
on developers site.
However, there are some drawbacks of using
. For example, it is difficult to control
tags
consistency. If, by mistake, a transition to nowhere will be declared,
it will be hard to debug such a model.
As a solution that added to the library's capabilities - new keywords were added
to the syntax of model description -
and
. What does it mean:
- Adding
to the vertex means that there is a transition (edge) with the specified name from this vertex to some other vertex beyond the graph.OUTDEGREE - Adding the
label to the vertex means the opposite, namely the existence of a transition (edge) to the current vertex from some external graph.INDEGREE
and
functionality provides us an ability to split huge test model graph into small, clear sub-graphs.
When executing
- it builds, a union of all the sub-graphs into one temporary *.graphml file and put it into the
/link (resources) subdirectory. The union graph can be used:
- to generate code interfaces to be implemented;
- to check test model consistency;
- to debug tests.
Test model, split into sub-graphs (vs the "monolithic" test model) has better maintainability. For example, in the case of a merge conflict, it will be much easier to resolve it in one small sub-graph file, rather than in one, huge, monolithic test model file.
Using
we can implement some editional logics like:
INDEGREE: e_ClickHome /* Navigate home page */ [authorized==false], e_ClosePreview /* Close preview popup */;
In order not to duplicate the same command for a set of transitions to
the same state, as well as to be able to specify similar commands for
connections, the keyword SET was introduced.
![SET example](docs/SET keyword.png?raw=true "SET keyword example")
Simplified timer integration into vertices
The template of the generated Java file has been changed.
Generated interface methods based on vertices now return boolean value.
Thus, any vertex-based method will run until the interface logic returns
or timed out.
@code annotation with YEd syntax
![@code annotation](docs/code annotation.png?raw=true "@code annotation")
In some cases, information about the generated element (edge or vertex), is enough to generate interface implementation code. Here's what the generated code will look like for the example above.
// Generated by GraphWalker (http://www.graphwalker.org)package org.graphwalker.java.graphml;
import org.graphwalker.java.annotation.Model;import org.graphwalker.java.annotation.Vertex;import org.graphwalker.java.annotation.Edge;import static org.graphwalker.java.annotation.Dataset.*;
@Model(file = "com/avito/graphwalker/model/CodeExample.graphml")public interface CodeExample {
@Vertex(value = "@code isBrowserStarted(\"Firefox\")\\n browser started") default boolean v_BrowserStarted() { return isBrowserStarted("Firefox"); }
@Edge(value = "@code runBrowser(\"Firefox\")") default void e_init() { runBrowser("Firefox"); }
@Edge(value = "@code get(\\\"https://www.avito.com\\\");\\n Home page navigation\")") default void e_navigate() { get("https://www.avito.ru"); }
boolean isBrowserStarted(java.lang.String arg0);
void get(java.lang.String arg0);
void runBrowser(java.lang.String arg0);
@Vertex(value = "home page is open") boolean v_OpenHome();}
However, we should note that this feature is intended for generated models, not manually created.
Rule | Right syntax | Wrong syntax |
---|---|---|
only inside comment block |
|
|
semicolon to split |
|
|
no logical operators on the top |
|
|
only String, Number, Boolean... |
|
|
or other methods as parameters |
|
|
methods of the same names returns the same |
|
|
no other annotations |
|
|
Path generator with reachability validation
![Path generation](docs/Path generation.bmp?raw=true "path generation with reachability validation")
In the model above in order to get into
, you need
to set the necessary values of the guard variables
,
,
.
The only legal route to
is green colored. To set such a route using AStarPath,
we would have to describe an additional point
, otherwise the wrong
route
would be generated, and will cause runtime exception.
val combinedPathGenerator = CombinedPath()combinedPathGenerator.addPathGenerator(AStarPath(ReachedVertex("v1")))combinedPathGenerator.addPathGenerator(AStarPath(ReachedVertex("v7")))
Using the
in path generation,
it will be enough to specify only the final vertex.
val pathGenerator = ShortestPath(ReachedVertex("v7"))
It is important to note that generated paths can only contain unique vertices and edges,
so a route like
(cycles or loops) will not be generated.
Factory method pattern to decouple implementation
Let's say there is a model made up of many individual sub-graphs.
class Model : ExecutionContext(), EntryModel, Attributes, AddAttribute, AddCategory, Dependencies, ChangeDependencyBasedOnAPI, ChangeCustomDependency, EditAttributeWithValueSelection, EditGroupAttribute, EditCategory, EditField, EditTextAttribute, EditValue, CreateDependency, CreateDependencyBasedOnAPI, CreateCustomDependency, CreateValue, CreateForm, CreateStep, Values, ManageForm, Forms, AddBranch, ChangeBranch, DeleteValue, DeleteBranch, Vocabularies, Categories, ReleaseBranch{ /* implementation goes here */ }
With implementations of that scale, it becomes more difficult to deal with name collisions as well as change individual parts. As an alternative to this solution, the ContextFactory interface will be generated in target / generated-sources, by implementing it, you can decouple sub-graph implementations.
class Model : ExecutionContext(), ContextFactory {
override fun getEntryModel(): EntryModel = EntryModel() override fun getAttributes(): Attributes = Attributes() /* and so on */}
Parametrized tests
Parameterized tests can significantly reduce test model size and also make it much more readable. The area between two specially marked states will be replicated on as many separate automated tests as many records are in dataset. The parameterized automated tests themselves can be run in parallel with each other, thereby reducing the overall time.
Different types of parametrization
For parametrization both separate edges and parts of the graph built of several edges and vertices are available.
Syntax
To parameterize one single edge, you need to edit its label in the yEd editor. To do this, you can right-click on the selected edge and select "Add label". Then you need to insert the HTML table code. Unfortunately, yEd does not provide convenient ways to create such kind of tables, so you have to edit the HTML code in the editor itself. Read more about the features of yEd here and here. Table example can be taken from here and copy-pasted via the clipboard.
- Dataset with two rows with
andlabel
parameterss_trg
<html>e_ClickTransport<br/>/* Navigate Transport */ <table><tr> <th bgcolor="lime">label</th> <th bgcolor="yellow">s_trg</th></tr><tr><td bgcolor="lime">Car</td><td bgcolor="yellow">6</td></tr><tr><td bgcolor="lime">"Truck"</td><td bgcolor="yellow">10</td></tr></table></html>
- Dataset with a single parameter
<html> e_FillCredentials /* Fill credentials */ <table><tr bgcolor="lime"> <th>username</th> </tr><tr bgcolor="yellow"><td>admin</td></tr><tr bgcolor="orange"><td>root</td></tr></table></html>
- Multi-row dataset with
,label
,s_trg
parametersAB_test
<html>e_ClickTransport<br/>/* @code clickLink(${label}, ${s_trg}); Navigate Transport */ <table><tr bgcolor="lime"> <th>label</th> <th>s_trg</th><th>AB_test</th></tr><tr bgcolor="yellow"><td>Car</td><td>6</td><td>false</td></tr><tr bgcolor="orange"><td>Truck</td><td>10</td><td>false</td></tr></table></html>
Warning - String, Boolean, Numeric (int / double) data types are only supported.
Only one type of data can be declared per column. The parameter of the String type
can be declared as a single word or, if it consists of several words, quoted like -
.
In order to parametrize graph linear section (like octopus tentacle), it is necessary to connect two points of the section of the graph with the new edge. The starting point or root dataset is a vertex that will not be parameterized. The final is the vertex that will be parameterized. But at the same time, all transitions from that edge will no longer be parameterized.
The only difference between such a connecting edge and the previous version is that nothing is declared in it, except for the HTML table code. It contains neither the name of the edge, nor the text description, nor the weight or other parameters.
- Dual-row dataset with
andlabel
parameterss_trg
<html><table><tr> <th bgcolor="lime">label</th> <th bgcolor="yellow">s_trg</th></tr><tr><td bgcolor="lime">Car</td><td bgcolor="yellow">6</td></tr><tr><td bgcolor="lime">"Truck"</td><td bgcolor="yellow">10</td></tr></table></html>
Generated code
For all parameterized elements - edges and vertices inside the section of the graph, including the final vertex, graphwalker library will generate methods like this
@Model(file = "my/company/graph/Model.graphml")public interface Model { @Edge(value = "@code clickLink(${label}, ${s_trg}); \nNavigate Transport") @Row(value = { @Value(name = "label", value = "Car"), @Value(name = "s_trg", value = "6") }) @Row(value = { @Value(name = "label", value = "Truck"), @Value(name = "s_trg", value = "10") }) default void e_ClickTransport(java.lang.String label, double s_trg) { clickLink(label, s_trg); } /* and so on */}
Proper method arguments will be passed in runtime.
How to use
Run your datasets like this
// dataset parameter nameval name: String = "some name"// number in range [0; datasetSize-1]val id: Int = 0// number of possible ways in datasetval size: Int = 2val dataset = Dataset(name, id, size)val shortestPath = ShortestPath(ReachedVertex(groupName, vertexName), dataset)
Known restrictions
- datasets nested into each other are prohibited
- the parameterized part of the graph must not contain branches, including
/INDEGREE
labels.OUTDEGREE - the parameterized part of the graph should fit into one file, while there may be several such sections on one file, as well as several files with (different) datasets
Original authors
- Nils Olsson
- Kristian Karl
Modified code authors
- Ivan Bonkin (inbonkin@avito.ru)
- Andrey Petukhov (anpetukhov@avito.ru)
- Mikhail Lavrik (myulavrik@avito.ru)
Licence
MIT