transformed into our internal representation. The
transformation uses Lodash (Lodash) library as a
functional paradigm helper. JS test generator is
executed on this internal representation and prepares
the textual output, which then will be saved in the test
files. Test generation itself consists of three stages
and is repeated for each class. All members of a given
class are considered, also those inherited from
superclasses, as all generalization hierarchy is
traversed during test generation.
Firstly, the class under test from the source code
is imported into the unit test file. Then, for each
attribute which has type defined in the class diagram,
a separate test is created. Unlike (Pires et al., 2008),
we test the code written in a dynamically typed
language, so types have to be determined in runtime.
Thus, in every test an instance of the tested class is
created. In the second line of each test there is a
comparison of types. We write an assertion to check
if the type of the implemented attribute is the same as
in the UML model. The type from the UML model is
read from our internal project representation and
hardcoded in the test code. The actual attribute from
implementation is obtained dynamically from the
previously created instance of the class under test. To
check its type, we use JS typeof operator.
The last stage of class test generation is similar.
Tests are generated for each method, for which the
return type was defined and is a primitive JS type.
Again, the type returned by the implemented method
can be obtained only dynamically, so the method
needs to be executed within the test. An instance of
the class is created in every such test. Moreover, to
execute a method, correct arguments need to be
provided. We achieve this by defining mock-ups of
all parameters. The only worth information is the type
of the return value, so we can pass any values of
arguments of expected types. We randomly select
values of parameters using Chance (Chance) library.
As the tests have to be repeatable, we define a
constant seed at the first usage of this library. The
result of method execution is assigned to a variable.
Finally, the assertion checks whether the type of this
variable is compliant with the hardcoded type
obtained from the class diagram.
4.3 Modifications of Algorithm based
on Activity Diagrams
Our adaptation of the method proposed by (Kurth et
al., 2014) is also very different from the original. The
aforementioned paper describes only test data
generation, while we propose a fully automatic
approach to unit tests generation. Here we assume the
correctness of the input diagrams again. The
constraint solver we use, Constrained (Constrained),
supports only integer variables, so we assume that all
variables in the input activity diagram are integers.
We also expect that the initial node of the diagram is
related to a note with a method signature. As in the
original solution (Kurth et al., 2014), all actions can
have notes with their local postconditions, possibly
with @pre marks, and guards can be placed on
control flows. All constraints should be written
according to the specified grammar (Małkiewicz-
Błotniak, 2020) for the subset of OCL language. Test
conditions are extracted automatically from the
activity diagram.
Similarly to the adaptation described in the
previous section, we transform the StarUML project
into our own internal representation. Then, test cases,
which will be stored in test files, are obtained. Here
we use our adaptation of (Kurth et al., 2014) method.
Firstly, it is necessary to generate all possible paths in
the graph representing the input activity diagram. We
use Graphlib (Graphlib) library to perform operations
on the graph. All paths are found using the modified
version of DFS algorithm. The authors of the original
method also used modified DFS, but their
modification focused on infeasible path elimination
(Kurth et al., 2014), while our version of DFS ensures
that all of the paths are found, even if they have some
nodes in common. We achieve this by marking
currently processed node on the stack as unvisited
after successfully finding a path.
Once all paths are obtained, values of test data (i.e.
parameters of the method, referenced attributes of the
enclosing class and expected return value) for each
path are generated. They are calculated with the help
of the constraint solver, we have chosen a JS tool
named Constrained (Constrained). This solver has
some limitations, e.g. it supports only integer
variables, but has also a great advantage – it always
finds boundary values satisfying given condition, so
no separate boundary value analysis is necessary.
Before using the solver, the string representations of
constraints are parsed. We have defined a simplified
grammar of a subset of OCL language, which can be
found in (Małkiewicz-Błotniak, 2020). The parser for
this grammar is implemented with the help of Nearley
(Nearley) library and employs a lexer defined using
Moo (Moo) library. To analyze abstract syntax trees,
we use functional paradigm utilities provided by the
Lodash (Lodash) library. All new variables and
constraints are added to the Constrained solver. As
this tool supports only non-strict inequalities, we have
added a transformation that allows for usage of all
types of inequalities on the input diagram. Strict