This is the second post in a series that will address some of the problems and questions surrounding the usage of the Robot Framework (RF), that I encounter frequently in the field. Click here to take a look at the first of these posts. As I have stated in the introduction to that post, I will sometimes use these treatments also as an opportunity to make small digressions, so as to shed some light on the internals of the RF.

This post will show you how to implement a simple and yet very efficient mechanism for resource sharing within the framework. Actually, I will use three separate posts to do so. The post you're reading now, will be the first of these.

Robot Framework provides an out-of-the-box mechanism for sharing, i.e. re-using, various types of resources. However, depending on the size and complexity of your test automation project, this mechanism can sometimes entail quite some maintenance work in and of itself. In three posts I will describe a setup for sharing, that will virtually eliminate the involved maintenance effort. Additionally, this setup will provide you with a few, very fundamental, abstraction layers that you will have to apply in any given test project.

So, let's first have a look at the main types of RF resources and at how these resources can be reused within the RF (first post). Then we will describe the problem surrounding this native mechanism (second post). Finally, we'll be providing a solution to this problem (third and final post).

Reusable resources

At the highest level, there are two kinds of resources that can be shared throughout your RF test code base: third-party test libraries and user defined resources. Let's take a closer look at both of these.

First kind: Test libraries

As I have already mentioned in some of my other posts, Robot Framework exists within an extremely rich ecosystem of shared test libraries. Due to the highly committed, very enthusiastic and hence very active RF community, this ecosystem is growing fast. Currently, it is already containing dozens of such test libraries.

These are libraries of keywords (i.e. reusable test functions) that you can use when developing your own automation code. The test libraries thus provide out-of-the-box test automation functionality. Functionality that you, consequently, do not need to develop yourself! Basically, there are two types of functionalities that are being provided by these test libraries.

Types of test libraries

Interface driver libraries

One type of library contains keywords for driving (i.e. automate against) a specific type of interface, whether it be a gui, api, service or protocol. Examples of such automation interfaces are REST, JDBC/ODBC, HTTP, SOAP, JMS or a Web GUI.

A few examples of this type of library are SeleniumLibrary (formerly Selenium2Library), SudsLibary, AndroidLibrary and HTTPLibrary.

Convenience libraries

The other type of library contains keywords that provide all kinds of convenience functions, such as splitting a string, making assertions on an xml file, reading from or writing to a file on disk or checking a certain process is running.

A few examples of this type of library are StringLibrary, XMLLibrary, SSHLibrary and OperatingSystemLibrary.

Example of a test library: the SudsLibrary

Using, for example, the SudsLibrary, it is possible to write domain functions for product functionality that is being exposed through a SOAP service, without having to write the code to actually drive that specific type of interface. The SudsLibrary abstracts from the SOAP-interface specifics and, as such, effectively provides you with out-of-the-box integration code. You can, therefore, concentrate fully on writing your domain-specific test functions and, based thereon, on writing your automated test cases.

For instance, by making two simple, parameterized calls within your domain function to the relevant library keywords, the SudsLibrary can create a SOAP client (proxy) for you (through consuming the specified WSDL). This client can then generate a properly structured SOAP message (based on the service WSDL in combination with the specified method name and arguments) and also make the actual (http wrapped) call to the SOAP service. After receiving the response from the SOAP service, the SOAP client will return the response object for further use in the remainder of your code. For example, you could write the following RF script code:

Create SOAP Client    http://www.webservicex.net/globalweather.asmx?WSDL alias=WeatherService
${response}=    Call SOAP method    GetCitiesByCountry    France

These keywords abstract (and thereby remove you) from the complexities of interacting with a SOAP service, allowing for more concise and simpler statements in your own test functions. The library keywords themselves contain approximately a few hundred lines of code (including annotations) in order to facilitate this simplicity.

The SudsLibrary thus contains the actual intelligence/logic to interact with a SOAP service and through it the RF has out-of-the-box access to such a service type.

Digression: A small peek on test library internals

RF's SOAP testing library is called 'SudsLibrary' (and not, for example, SOAPLibrary), because it actually wraps and thus reuses an existing (open source) Python SOAP client, named 'Suds'. A lot (if not most) of the RF test libraries actually wrap existing, third-party Python (or Java) clients. For instance the two HTTP libraries and each of the libraries for mobile testing. Or, rather obviously, the SeleniumLibrary. Such test libraries reuse (expose) all or some of the functions contained in those clients, by making them available through RF's 'library API'. To do so, they add merely a relatively thin layer of integration/glue/wrapper code on top of the existing client. The wrapper functions of the test library contain calls to the client functions (in compliance with the client's api), sometimes combining (within the same keyword) multiple calls to various client functions, to provide you with broader, more powerful or more flexible test functions. Through the latter an extra level of convenience is added.

For instance, the above script code uses the 'Create SOAP Client' keyword. This keyword is available through the SudsLibrary. The SudsLibrary has a module (called 'clientmanagement.py'), which contains a 'create_soap_client' Python function that implements the keyword.

The 'create_soap_client' function of the SudsLibrary.

This keyword, in turn, contains a call to a class 'client' within a module of Suds (named 'client.py') which does the actual work so as to create a SOAP client as described above.

The 'Client' class of the Suds client

We can also create a logical view:

Logical view of the flow through the stack when testing against a SOAP service.

Or, if we wanted to test at the GUI-level, the flow through the stack could be:

Logical view of the flow through the stack when testing against a web GUI.

Etcetera!

Note that not all RF libraries wrap existing clients. For instance the BuiltInLibrary consists merely of a set of small Python functions (that have been written for your convenience). Similarly, the RF XMLLibrary does not wrap a client, but programmatically uses the Python Element Tree API (see the last two pictures).

Second kind: User defined resources

A note on terminology

Now, I know: the following might be confusing. But please, bear with me! 🙂

Unfortunately, the RF community chose to refer to the second type of resource (i.e. the user defined resource), with the rather generic term 'resource'.

Therefore, in the remainder of this post, I will be using 'resource' as I have done up until now, namely as a generic term to designate both test libraries and user defined resources as reusable assets. At the same time, I will use 'user defined resource' or the term 'resource' enclosed within single quotes (so: 'resource') to designate merely the specific, user-defined type of a 'resource' as meant by the RF community and as explicated below. But please be aware that within the RF ecosystem, a user defined resource is generally referred to as simply a 'resource'.

So, semantic hair-splitting aside, let's continue with the story!

'Resource' and resource file

Using the functionality provided in the third-party test libraries (as described in the previous section), you can then proceed to write (one or more layers of) your own, domain specific test functions, called 'user keywords'. (For a first, short introduction into the subject of test code layering, see, for example, this blog post.) The calls to these library keywords will be embedded in your control/flow logic, so it is actually a lot like programming against an API.

For instance, in the case of testing a web shop at the GUI level, you can use the keywords from the SeleniumLibrary (and other libraries) to write user keywords to automatically drive, for example, the product functionality with which to add new products to your shop. Or a set of user keywords to automate the product functionality with which you can search for existing products.

These (highest-level) user defined keywords constitute, in turn, the vocabulary of the domain specific language in which you will specify your test designs.

The phrase 'user keyword' has, thus, been coined to designate keywords written by us, the RF users, and to be able to distinguish these from the keywords that are being provided by existing, third-party test libraries (such as the SeleniumLibrary). These latter are fittingly referred to as 'library keywords'.

What is important here, is that the RF provides the means to bundle user keywords that are logically/functionally related, into your own user keyword libraries. For example, you might extract all of the user keywords related to your product's search functionality into an appropriately named keyword library. This user defined library can then be shared throughout (parts of) your automation code base, so as to be able to reuse the test functions (keywords) contained therein.

As mentioned above, within the RF such a user defined keyword library is called a 'resource', to be able to distinguish it from the existing, third-party 'test libraries' within the RF ecosystem.  Physically, a user defined resource, such as a user keyword library, is a file. A plain text file (as we will see later on), to be precise, that contains the code that implements the various keywords. Accordingly, such files are called 'resource files'.

Aside from test functions (keywords), resource files can also contain other types of user defined resources, such as (global) variables or object maps/repositories (although, technically, the latter are also just variable assignments). The differences between these types of user defined resource bear no relevance to our topic, so I will not further elaborate on them in this post. For those readers looking for more details on the various types of user defined resources that can be created, please see the corresponding sections of the RF user guide.

How to make both kinds of resources available to the Robot Framework?

To be able to appreciate the problem at hand, we first need to go into just a few details regarding the organizing principles, according to which the RF structures a test project into folders and files on the file system.

Of files and folders

At the most basic level you will use the RF to create, run and maintain test cases and user defined resources (such as user keywords) within your test project. At any given time, within that process, existing resources function as building blocks.

That is, you will start with having only library keywords available, provided by test libraries such as the SeleniumLibrary. Utilizing these, you will then proceed to create (various layers of) your own, domain-specific keywords (i.e. user-keywords), by calling the library keywords from within your own function's flow-logic. The third and final step (at least within this highly simplified, high-level overview of the process) is the specification of test cases in terms of user-keyword calls.

Everything you write in the RF, whether it be test cases or user keywords (or other such user defined resources), is saved as plain text in a text file. A text file containing a set of one or more (usually thematically related) test cases is called a 'test suite file' and a text file containing a user defined resource, such as a collection of (usually functionally related) keywords, is called a 'resource file' (as also mentioned in the previous section).

Depending on the size and complexity of your test project, you can further organize these files logically into a structured hierarchy of folders (in accordance with the desired functional/logical break-down of your project), so as to be able to provide a transparent, comprehensible and maintainable overview of the project. Folders that contain one or more test suite files, are called 'test suite' as well. Such test suite folders may also contain one or more (structures of) test suite folders, next to the test suite files that they may contain. The test suite files are always the lowest-level test suites within this hierarchy and it is in these files that the actual test cases are contained. Finally, these test suite folders can also contain resource files, for instance a resource file containing the keywords required for the various suites of test cases within that folder.Simple example structure.This is, most obviously, not meant as an example on how to structure a test project, but meant merely as an illustration of what is has been said in this section. Note further that this view is of the RF editor 'RIDE'. What you see in the picture is not the RF. So please don't be put off by the look and feel of this editor (which is not uncommon, actually).

Importing

RF artifacts

We have now encountered the following types of RF artifacts:

  • Test suite folder: containing one or more test suite folders and/or one or more test suite files
  • Test suite file: containing one or more test cases
  • Resource file: containing one or more user defined resources, such as keywords and/or variables
  • Test library: e.g. a Python module or Java library, providing a set of library keywords

As mentioned before, a test suite file may also contain resources, i.e. keywords and/or sets of variables that are specific (i.e. scoped) to one or more of the contained test cases. But we can ignore that here because, generally, all resources should be (and generally are) extracted into resource files. Because of the latter test suite folders may, and often will, also contain resource files. However, in the remainder of this post, I will promote and work towards an approach in which test suite folders will never contain resource files.

Which ones of these can be reused?

Of course, the artifacts that are (potentially) reusable, are the test libraries and the resource files.

However, since these are separate and independent artifacts, none of them has any 'knowledge' of the (existence of the) others. Therefore, in order to be able to reuse something, we will have to make the 'consuming' artifact 'aware' of the existence of the 'providing' artifact. In RF, as in other, similar frameworks, we do that through an import statement, just as we would do in a Python module or a Java class.

Here we would be reusing two external test libraries as well as two user defined resource files:

Examples of import statements

Which ones of these will be reusing others?

The artifacts that can contain such import statements, so as to be able reuse other artifacts, are:

  • a test suite folder (within an __init__.txt file, which will make Python treat the folder as a package):
    • why: typically to be able to define set-ups/tear-downs at the required parent-suite levels
    • what: therefore it will mostly import user defined resources (but sometimes also test libraries), since such set-ups/tear-downs usually need to be domain specific
  • a test suite file:
    • why: to be able to write test cases in terms of domain specific functions
    • what: therefore it will typically import resource files and not test libraries, since test cases should contain no code and no lowest-level, technical work-flow steps
  • a resource file:
    • why: as mentioned before, user defined resources (such as a user keyword) are built through reusing existing resources in test libraries and/or resource files
    • what: therefore it will mostly import test libraries, but also other resource files, such as variable files and/or user keyword libraries; note, the latter will generally concern only libraries which contain technical 'helper' keywords, since it is a bad practice to have functional domain functions call other functional domain functions (apart from possible wrapper functions); the latter is subject to discussion though

What is 'importing' then?

Through importing we basically tell the RF to look for a certain resource (in the generic sense) at a certain location (or at certain locations) and, if it finds it, to integrate these resources and make them available to us, effectively extending its capabilities with these resources.

Thus, when the RF encounters an import statement for a test library, it will try to find a module with the given name in (any of) the file system location(s) in which such modules are typically installed. This is one of the reasons why we have to specify certain values for the PATH system variable after having installed Python. Similarly when running on Jython, the classpath may be of relevance to finding Java modules. And yet another way of telling RF where to look for possible resources is the PYTHONPATH system variable, as is the case with e.g. the RemoteSwingLibrary. When the RF finds the module (and it has been properly implemented, in accordance with the RF library API), the resources contained within that module will then be made available for usage to us (and our code) as well as for test execution at run-time. When the RF doesn't find the module or if it detects a problem with it, it will raise an exception, providing you with a relevant error message.

Similarly, when importing a user defined resource, such as a user keyword library, the RF will look for the resource file in the file system location as specified in the import statement and, if it finds it and it is implemented correctly, the RF will make it available.

An imported external test library

An imported external test library

An imported user defined resource

An imported user defined resource

So, to be precise, it is the RF that is made aware of the existence of a resource instead of making an artifact aware of the existence of other artifacts. Through an import statement, the RF is extended with the capabilities of the imported artifact and it will then be possible to include the functions of that artifact into your calling structures.

Up next: The problem

We now have an understanding about the types of resources that can be shared and about the mechanism that implements, enables and facilitates this sharing. Such a mechanism, however, comes with a price. In the next post we will take a closer look at the involved 'costs'. The third and final post will introduce a way to minimize these costs.