ElementaryBlock Element/en
Introduction[Bearbeiten]
Elementary blocks provide the basic low-level functionality of an expecco testsuite. They are used for operations which are not broken down into more elementary subactivities, and which are therefore not representable as an activity diagram ("Compound action"). Typically, operations described as elementary action block are numeric calculations, low level string, stream or collection operations, interfaces to protocols or to external devices. Especially domain specific low-level interfaces to local or remote processes, document converters, databases and hardware devices are implemented as eleemntary block.
Also, elementary actions are typically executed much faster than compound (diagram) actions. So also performance critical operations are sometimes writen as elementary blocks.
The behavior of an elementary block is defined by one of the following:
- a piece of textual program code, which executes inside expecco, and is written in one of the directly supported and builtin programming languages (currently: Smalltalk or a JavaScript dialect)
- a piece of code, written in Groovy, which executes in a JVM on the local system or inside the system under test (SUT) itself
- a piece of code, written in VisualBasic Script, which executes in a separate script interpreter process (requires extra licensed plugin)
- a piece of code, which is executed by an external scripting language interpreter (shell, batch or ruby script)
- a call to a DLL (shared library) function (which is often, but not required to be) written in C or C++
- a call to an external program
- a remote procedure or service call (SOAP, Rest, XML-RPC, SunRPC)
Expecco provides built in language interpreters and compilers for two scripting languages: the built In Smalltalk and a built in JavaScript-like scripting language. When such an elementary block is executed, the script code runs inside the expecco process. These support high level symbolic debugging including single-step, breakpoints or inspection of variables. The debugger even allows for code changes to be done to the running program (for example, while the program is stopped at a breakpoint). These internal scripts are transparently and automatically compiled into machine code when first executed ("Just in Time" (JIT) compilation).
Additional builtin scripting languages (Ruby and Scheme) are being prepared and planned for future releases.
Other scripting languages (Ruby, Perl, TCL, Python, etc.) can be called via external script interpreters. It is also possible to call existing command line scripts (Shell under Unix or Windows+cygwin, Batch under Windows).
With the Java Bridge extension plugin, elementary blocks can also be written in Groovy, which is a scripting language with a syntax similar to Java, and which is transparently (and dynamically) compiled to Java bytecode, to be executed inside the SUT or on the expecco host or on any other such configured machine in the local network. Groovy blocks are very useful to define Java callbacks, subclasses of existing Java classes or to create and manipulate complicated Java objects inside a tested Java application. When such a block is executed, the script code runs inside a Java-VM, which can be either a local one, dedicated to script execution, or on the target system (even inside the SUT, if it is written in Java). More detail is found in the Expecco API Documentation.
With the VisualBasic Plugin, elementary blocks can be written in the VisualBasic Script syntax and are executed in an external VisualBasic interpreter. This may run on the local host (the one on which expecco runs) or optionally on a remote host. The host on which the script engine executes must be a Windows host. For expecco users working under Unix/Linux or OSX, this means that at least a virtual (or real) machine with a Windows system must be setup and reachable in the network, and that the Windows machine serves as a script execution server for the Unix machine.
The source code is usually edited in the code editor. It is interpreted according to the used language’s syntax at runtime. For the builtin languages Smalltalk and JavaScript, it is possible to switch the syntax pattern for already implemented blocks, causing the code to be automatically converted to that particular syntax style. The values of data pins can be directly accessed (read and write) in the code using the pin's name. The external interface of an elementary block (e.g. number of pins) is defined in the schema editor.
Notice that the standard library as delivered already contains a number of useful elementary blocks for a wide range of applications. So that the need to define your own elementary blocks is often limited to special interfacing or specialized processing of data. Depending on the testing domain (technical/business/UI-testing), many expecco users even do not need to write any elementary actions and can realize their tests using diagrams only - this is especially the case in the area of UI testing. On the other side, others may depend heavily on the ability to interact directly with deep internals of the SUT, for example to call Java functions with complex object interfaces. Expecco tries hard to provide a convenient framework for both ends of this spectrum, by integerating elementary code development seamless into the test development process.
Smalltalk and JavaScript Block Examples[Bearbeiten]
The following examples are meant as tutorial and introductionary examples. They are written to be easy to understand, not to use the full power of the underlying languages (eg. some could be written much shorter, by using more sophisticated existing library functions).
Don't worry: you will not need to go down to that low level programming to get started with expecco. All of the functionality presented below is already present in the Standard Library and can be used "out of the box". However, it may be good to know, that there are virtually no limitations in what you can do in expecco, and that whenever you need additional functions which the Standard Library did not foresee, you can add it in a few minutes.
Simple String Processing Example[Bearbeiten]
The first example simply takes a string and cuts off some characters at either side, and returns some middle part of it. To make the example more interesting, the parts to be cut off shall be the first 5 characters at the left, and everything after the last separator (blank character or tab). We first define the external interface of that action block to consist of an input-pin named "inString" and an output-pin named "outString". Both pins are declared as being of "String"-datatype, this ensures, that when placed into a diagram, only other String-typed pins can be connected to it. The so called "schema" definition of the action block is therefore:
Then, write the code, either in Smalltalk as:
execute
|inStringValue|
"/ fetch the data value from the pin
inStringValue := inString value.
"/ find the last separator in the string
indexOfLastSeparator := inStringValue lastIndexOfSeparator.
"/ extract the middle part
middlePart := inStringValue copyFrom:6 to:(indexOfLastSeparator-1).
"/ write to the output pin
outString value: middlePart
The same operation written in JavaScript as:
execute() {
var inStringValue;
// fetch the data value from the pin
inStringValue = inString.value();
// find the last separator in the string
indexOfLastSeparator = inStringValue.lastIndexOfSeparator();
// extract the middle part
middlePart = inStringValue.copyFrom_to(6, indexOfLastSeparator-1);
// write to the output pin
outString.value(middlePart);
You should always provide a little test and demo of your elementary actions in the Test/Demo page. There, place at least one instance of the block as a step, give it its required inputs and execute it there. For example:
Number Reading Example[Bearbeiten]
The next example takes a string and reads three numbers from it, which it returns as a 3 element vector (tuple). This could be useful as part of a reader for test data, or to process user input. The three numbers will be separated by spaces or tabs. It has a String-typed input pin named "inString" and an Array-typed output pin named "numberVector". It will report an error if not exactly 3 numbers are contained in the string:
execute
|inStringValue inStream n1 n2 n3|
"/ fetch the string from the pin
inStringValue := inString value.
"/ get a read-stream on the string's characters
inStream := ReadStream on:inStringValue.
"/ read the first number
n1 := Number readFrom:inStream onError:[ self error:'error reading the first number'].
"/ and so on...
n2 := Number readFrom:inStream onError:[ self error:'error reading the second number'].
n3 := Number readFrom:inStream onError:[ self error:'error reading the third number'].
"/ check, if the end of the stream has been reached (eg. nothing more is there)
"/ but allow additional spaces
inStream skipSeparators.
inStream atEnd ifFalse:[
self error:'garbage after the third number'
].
"/ create a vector and write it to the output pin
outString value: { n1 . n2 . n3 }
The same operation written in JavaScript as:
execute() {
var inStringValue, inStream, n1, n2, n3;
// fetch the data value from the pin
inStringValue = inString.value();
// get a read-stream on the string's characters
inStream = ReadStream.on(inStringValue);
// read the numbers
n1 = Number.readFrom_onError(inStream, function() { this.error("error reading the first number");});
n2 = Number.readFrom_onError(inStream, function() { this.error("error reading the second number");});
n3 = Number.readFrom_onError(inStream, function() { this.error("error reading the third number");});
// check, if the end of the stream has been reached (eg. nothing more is there)
// but allow additional spaces
inStream.skipSeparators();
if (! inStream.atEnd) {
this.error("garbage after the third number");
}
// create a vector and write it to the output pin
outString.value( [ n1 , n2 , n3 ] );
Please notice the inner function argument in the above code, which is the error call back argument to the "Number readFrom_onError" call. In Smalltalk, this is simply written as a block "[...]", whereas in Javascript the ugly "function() {...}" syntax is needed. This is one of the examples, where Smalltalk code is much easier to write and understand.ss
Minimum and Maximum of an arbitrary Function[Bearbeiten]
Here is a block to compute the minimum and maximum values of a mathematical function over a range of x-values. It is especially flexible, as the name of the function can be passed as an argument (for example: 'sin', 'cos' or 'arcTan'). The block's definition is:
and its code could be written in Smalltalk as:
execute
|fn minY maxY y|
fn := functionName value asSymbol.
(x0 value) to:(x1 value) by:(dX value) do:[:x |
y := x perform:fn.
minY := minY isNil ifTrue:[y] ifFalse:[ minY min:y ].
maxY := maxY isNil ifTrue:[y] ifFalse:[ maxY max:y ].
].
min value:minY.
max value:maxY.
or in JavaScript as:
execute() {
var fn, minY, maxY, x, y;
fn = functionName.value().asSymbol();
for (x = x0.value(); x <= x1.value(); x += dX.value()) {
y = x.perform(fn);
minY = (minY.isNil()) ? y : minY.min(y);
maxY = (maxY.isNil()) ? y : maxY.max(y);
}
min.value(minY);
max.value(maxY);
}
(notice the use of the "perform"-function, to call a function by name)
Of course, as usual, there are multiple ways to write the same function; an experienced Smalltalker would write:
execute
|fn minMax|
fn := functionName value asSymbol.
minMax := ((x0 value) to:(x1 value) by:(dX value))
collect:[:x | x perform:fn])
minMax.
min value:minMax first.
max value:minMax second.
and a JavaScript programmer might prefer:
execute() {
var fn, minY, maxY, x, y;
fn = functionName.value;
for (x = x0.value; x <= x1.value; x += dX.value) {
y = x.perform(fn.asSymbol());
minY = (minY == null) ? y : Math.min(minY,y);
maxY = (maxY == null) ? y : Math.max(maxY,y);
}
min.value(minY);
max.value(maxY);
}
The block could be used to calculate the min/max of the sine function in the range 0..1 as follows:
Multi-Setter with Variable Number of Input Pins[Bearbeiten]
The following defines a block to set multiple key-value associations in a dictionary object.
In the schema, the last two input pins were defined as a variable-pin-group (using the second to last pin's "special attribute" menu).
Therefore, the last two pins can be replicated an arbitrary number of times in a step.
The block's elementary code must of course be prepared for this, and loop over the step's number of input pin values.
The block's definition is:
and its code could be written in Smalltalk as:
execute
|dictionary|
dictionary := dictionaryInPin value.
1 to: keyPin size do:[:index |
|eachKey eachValue|
eachKey := keyPin valueAt:index.
eachValue := valuePin valueAt:index.
dictionary at:eachKey put:eachValue.
].
dictionaryOutPin value:dictionary.
or in JavaScript as:
execute() {
var dictionary;
dictionary = dictionaryInPin.value;
for (var index = 1; index <= keyPin.size; index++) {
var eachKey, eachValue;
eachKey = keyPin.valueAt(index);
eachValue = valuePin.valueAt(index);
dictionary[eachKey] = eachValue;
}
dictionaryOutPin.value( dictionary );
}
A step to set 5 key-value pairs would look as follows (input pin group replicated 4 more times):
Groovy Blocks[Bearbeiten]
A Groovy block's code is executed in a Java Virtual Machine (either locally or in a remote system). It is described in more detail in the Expecco API documentation on Groovy .
VisualBasic Script Blocks[Bearbeiten]
A VisualBasic Script block's code is executed by a scripting host (see Microsoft documentation on Visual Basic). It is described in more detail in the Expecco API documentation on VisualBasic and the VisualBasic Plugin documentation.
Shell Script Blocks[Bearbeiten]
Under Unix operating systems (incl. Linux, Solaris etc.) or Windows with Cygwin installed (or any similar shell implementation which is bash/ksh compatible), you can also execute shell-script blocks. For example, here is a script to read its input, filter all lines beginning with some pattern, sort them, and pass them on as output:
grep "^xx" | sort
of course, arbitrarily commands can be called (even programs with their own GUI). Under Windows, make sure that you have installed an appropriate shell emulator, and that it is either found along the PATH, or that you have specified the sh-path in the expecco-settings dialog (in "External Tools" - "Shell Path"). You can also define an environment variable named "SHELL", containing the path to a shell interpreter. This is convenient, if the shell path is to be determined dynamically, or if another language interpreter is to be used (for example, by defining a local environment variable, and setting it to "/bin/perl", the script will be passed to a perl language interpreter).
Shell Script blocks provide the standard output and standard error at their output pins as text, so you may have to read and analyze that further.
Notice, that these output pins are buffered by default. This has the consequence, that expecco waits for the command to finish, and only writes those pins if the exit status is 0 (i.e. "No Error"). This may be inconvenient in two situations:
- if the called program/script interpreter returns an invalid exit status
- if you are interested in the output, either during the execution, or in the error case.
Then, you should change the "Buffered" attribute of those pins to "Unbuffered".
In addition, the settings dialog contains a "Log Stdout/Stderr on Transcript" flag setting in the "Execution-Logging" section. If this flag is set, all output from shell, batch and other external script execution is also shown in the Transcript window.
By default, the scripting interpreter is executing in the "current directory", which may be inappropriate, if expecco is started via a click from the desktop (depending on the Operating System, this may be the expecco-installation directory, the users home directory or any other). Therefore, you may want to make sure, that the execution is performed in a definite directory, especially if the script needs to refer to other files from you project or system. For this, either provide an execution directory via the blocks input pin, or specify it in the editor (which is effectively defining the directory as a freeze value).
The defined execution directory may contain placeholders in the form "$(xxx)", where "xxx" is either a shell environment variable, or one of the special builtin placeholder variables:
- $(AttachmentsDirectory) - the directory, where all attachments of the suite are located
- $(ExpeccoInstallationDirectory) - the directory, where expecco was installed
- $(ExecutionDirectory) - a temporary directory, which vanishes after the execution
- $(HomeDirectory) - the user's home directory
- $(TmpDirectory) - a temporary directory (subfolder of the system's tmp directory)
Especially $(AttachmentsDirectory) is useful, if your script refers to other scripts which are attached to the test suite (eg. if you have an attachment named "foo.sh", a shell script can call this with "sh foo.sh" iff the excution directory was specified as "$(AttachmentsDirectory)".
Variables are searched in any block-local environment, the test-suite environment, or the shell environment. Thus, you can also refer to shell variables such as "HOME" via "$(HOME)".
Batch Script Blocks[Bearbeiten]
These are similar to shell scripts, but use the Windows-batch file syntax. These cannot be executed on Unix systems, so your tests become OS-dependent, if you use them. The example code below would check out some source code from a cvs repository and start a build:
REM
REM batch file script (windows only)
REM
echo Checking out...
cvs co myProgram
cd myProgram
echo Building...
make
Batch Script blocks provide the standard output and standard error at their output pins as text, so you may have to read and analyze that further. Please read the paragraph on buffering the output and definition of the execution directory in the above "shell" section.
Ruby Script Blocks[Bearbeiten]
These are similar to shell scripts, but the script is executed by a ruby interpreter, instead of the sh (or bash). You have to ensure that an appropriate ruby interpreter is installed on the machine (it is not part of the expecco delivery package).
Similar to shell scripts, the ruby interpreter path can be either be left unspecified, or specified in the expecco settings (external tools), or via an expecco environment variable named "RUBY_SHELL"). The environment variable can be the global project environment, or the global test suite environment. If left unspecified, or without an absolute path, the ruby interpreter should be found along the PATH.
Ruby Script blocks provide the standard output and standard error at their output pins as text, so you may have to read and analyze that further. Please read the paragraph on buffering the output and definition of the execution directory in the above "shell" section.
DLL-Calls[Bearbeiten]
DLL-call blocks define an interface to a function which is contained inside a DLL (Dynamic Link Library). Typically, these are C or C++ functions. For a dll-call to be possible, the functions attributes have to be entered into the dll-call editor. These are:
- name of the dll (for example: "user32.dll"). If the DLL-name has no extension (".dll"/".so"), an OS-specific extension is added by expecco. For portability, it is better to only provide the base name here.
- the name of the function within the dll
- the calltype (WINAPI or C-Call).
- the number and types of the arguments
- the type of the return value.
In addition, it is possible to specifiy that the dll-call is to be executed by a separate API-call thread. This avoids a blocking of the expecco user interface and other executing activities, for long running dll-calls.
If the called function is a C++ function or C++ wrapper function, the compiler may have arbitrarily mangelt the name; usually a prefix or suffix, or both are added to the plain function's name. As this name mangeling is very OS- and compiler-specific, no automatic name generation can be done by expecco, and you will have to find out the name yourself, and paste it into the "C++ Name" field. Use any of your system's symbol-table listing tool ("nm", "dllWalker" etc.) to find out that name.
The WINAPI (or API-) calltype specifies that a different argument passing scheme is to be used. This is specific to MS-Windows DLLs, and even there, only to a subset of all libraries. Unless you have the C-header file parser plugin/extension installed, expecco has no builtin mechanism to figure out, which call convention is to be used for a particular DLL function. On windows, if you do not have the source code of your called function at hand, consult the C header file, where the called function's declaration is found. There, look for a WINAPI or API prefix. In general, the WINAPI interface is a historic leftover from ancient times, when Windows was written in Pascal. However, they are still used (for backward compatibility) in many system DLLs - especially in Kernel, User and GDI dlls.
If your testsuite is planned to be portable across operating systems (i.e. it should run under both Windows- and Unix test machines), you will need both a windows DLL ("foo.dll") and a unix shared library object ("foo.so" or "foo.dylib"). Because the filename extensions are OS-specific, it is recommended to enter the basename without suffix into the "DLL name" field. Expecco will fill in the extension as used by the OS (however, if your dll uses a non-standard extension, you will have to provide it).
DLL Mapping[Bearbeiten]
Expecco (actually Smalltalk) maintains a DLL-name mapping feature, which translates DLL-library names on an individual per-DLL basis. Thus, it is possible to control which DLL is actually used by DLL call blocks. The mapping is very useful, if multiple versions of a DLL are present (e.g. use "foo" in the DLL call block, and define a mapping of "foo" to "foo.vsnX") or to control from which location a library is to be loaded (e.g. define a mapping from "foo" to "/mylibs/libraries/foo.so").
The StandardLibrary contains a block "Define DLL Mapping", which modifies this mapping table.
SOAP-Calls[Bearbeiten]
These blocks define a remote procedure call via the SOAP protocol. SOAP blocks are normally generated automatically, by importing a wsdl service description. For more information, please refer to the WSDL Import Plugin description.
XML-RPC-Calls[Bearbeiten]
These blocks define XML-RPC remote procedure calls. XML-RPC is a very light-weight and low-overhead XML-based protocol, which is supported by many service oriented applications (among them, expecco and expeccoNET themself). For an XML-RPC call, the following information is mandatory:
- URL of the service (for example: "http://myhost:8080/rpc/xmlrpc")
- Name of the function (method)
There are 3 alternative ways, to specify the URL:
- hardwired as a string in the blocks specification (not recommended)
- via an environment variable
- via a pin value (freeze or dynamic)
Hardwired Service URL[Bearbeiten]
Because this is very unflexible, it should be only used for very constant URLs, or while exploring a service interface. For this, type the URL into the block's specification form.
Specifying the Service URL via an Environment Variable[Bearbeiten]
Use a macro-replacement pattern such as "%(RPC_HOST)" in the URL field (as above). This will be replaced by a corresponding variable name from the reachable environment variable named accordingly. You can either specify a full URL (i.e. host, port and path) or a partial URL using this mechanism. For example, you can also provide the URL via 3 separate environment variables, by entering: "%(RPC_HOST):%(RPC_PORT)/%(RPC_PATH)". A runtime error will be reported, if the variable expansion leads to an invalid URL. However, if a missing expansion leads to a valid URL, no error is reported. This allows for optional fields to be added to the URL, for example as in: "%(JIRA_HOST)%(OPTIONAL_RPC_PORT)/jira/rpc/xmlrpc".
Providing the URL via an Input Pin[Bearbeiten]
Add an input pin with the (obligatory) name "url", and deliver it a string value. This is the most flexible way, as the URL value can be dynamically generated by any other block, or be provided via a pin-freeze value. If a url-pin is present, but not connected, it is ignored and the above variable mechanism is used. Otherwise, the pin-value takes precedence over any value in the specification.
See Also[Bearbeiten]
For a list of already existing blocks, see Standard Library
For the programmer API for the built in languages see: Expecco API
For information on the functions avaliable in the built-in classes, please refer to the [Smalltalk/X Online Documentation] and especially the [Class Reference].
For example, some of the mathematical functions are described [here]
and String processing functions are found
[here] and
[here].