Skip to content
toroso edited this page Sep 20, 2014 · 10 revisions

The main purpose of Ruibarbo is to use it for test automation. This section contains information about how to use it as such, from a technical point of view but also from an organisational point of view.

What should your organisation look like?

Ruibarbo is built with agile values in mind and having a cross-functional team is preferred. In order to get the most out of your tests, "programmers" and "tester" should work together. If you do not have a co-located team you can still use Ruibarbo, but development will be slower. But then again, you are probably used to that...

Testability is a functional requirement that you ought to have on your design and architecture. Any design that is not testable is asking for trouble. If testing is not a priority among your "programmers" or "architects", you will have problems, not only using Ruibarbo, but in general.

In order to use Ruibarbo you will need programming skills. You will get the most out of it if you create an automation layer, as discussed in the Getting Started section. You also need access to the production code and might have to make modifications to it in order to attach Ruibarbo to your application and to make controls in your user interface easy to find.

What should be tested using Ruibarbo?

One of my favourite architectural patterns is Hexagonal Design a.k.a. Ports and Adapters (I prefer the latter name). It's a usable pattern not only for design discussions not also for test strategy discussions.

Ports and Adapters

Here is a short description:

The center of the hexagon is the core of your application. Inside it you speak the language of the domain. Pretty much every class and every property in it has a name that is understandable by the people who best understand your company's business (the domain experts). Herein lies your business logic.

However, your application is probably not an island, but needs to communicate with other applications or systems. Other systems could be for example a payment provider, a mail server, a database or a hardware driver. Such systems could be owned by other companies, owned by other organisational units within your company, or it could be owned by you. In the image, the other systems are placed on the outside of the hexagon.

These systems use a different language than yours since they work in a different domain (a domain with it's own domain experts).

A port is the connection to the another system. The port speaks the language of the other system. The port can either be outgoing, meaning that our system is the client and the other system is the server, or it is incoming where the roles are reversed[1]. The adapter's responsibility is to translate between your domain language and the language used in the other system.

Using the example of an SQL database, the port is the SQL connection and the language of that port is SQL. The adapter is something that creates SQL queries, e.g. an ORM tool or hand-written SQL.

Another example of another system, which is relevant for our context, are end users. According to this pattern, an end user is a system, located on the outer side of the hexagon. They communicate through the user interface, which is a port consisting of buttons, text boxes, etc. The adapter is the code behind the user interface. Probably you are using MVVM or a variant of that. Depending on how you implement MVVM (I've never seen two implementations that look the same), the View, View Model and Model are all part of the adapter (although I've seen variants where the Model is not).

So what has this got to do with testing? Everything, as we shall see.

It's ever so popular using BDD frameworks. BDD frameworks should be used to test business logic. The language used in the tests should be the language of the domain. This means that the BDD framework should not interact with any ports or adapters, but only talk to the application hexagon. That is, not only should the test only call methods declared within the application, but all adapters that the application hexagon interacts with should be replaced by test doubles[2]. No production adapter code should be executed from a BDD test.

What to test with BDD

Ruibarbo acts as a user and is communicating through the user interface port. Based on the previous section, this disqualifies Ruibarbo for testing business logic[3].

The adapters should be tested separately from the business logic. When testing outgoing adapters your test communicates with adapter code, and you might want to create test doubles for your ports. When testing incoming adapters, your test either interacts with the port or it acts as the port, and you could create test doubles either for the application hexagon or for the outgoing adapters[4].

You need different strategies and different tools depending on the kind of adapter. The aim of Ruibarbo is to help testing the MVVM adapter.

Test double for the application      or      Test double for the outgoing adapters

How do you create a good test?

Among many things, a good test is easy to read and easy to maintain. These two attributes go hand in hand, but I will dissect them separately.

  • Maintainability: Many intelligent people advocate that you should never make changes in test code and production code at the same time. They say: what good is a test as a safety net if you need to modify it when making changes to production code?

    This is a very wise statement, but how do you achieve it? How can you possibly, for instance, rename a production code method that is executed from a test without making changes to both at the same time? It seems practically impossible.

  • Readability: One of the most important attributes of a test is that it is readable and easy to understand. Tests occasionally break, and the person who has to figure out why is often not the person who wrote it. Or it might be the same person, but one year older and thus one year more senile.

One answer to both of these questions is to create an automation layer. In short, an automation layer is a domain specific language. It allows the tests to speak a language that is tailor made for the tests. It then communicates with the production code using the production code language. It is very similar to the adapters discussed in the previous section.

Automation layer

A language that is created specifically for the tests must make the tests easy to read, right? So that ought to be quite an enabler for solving the readability problem.

As it happens, the automation layer, if well written, also serves as a protection boundary between the production code and the test code. Using the example above with a rename of a production code method, that modification will spread into the automation layer. But the modification stops there, and the test remains untouched. That is, the test still serves as a safety net.

I'd argue that tests of all forms need some sort of automation layer. Sometimes it is just a method that the test code calls and that in turn calls the production code, that is, it is very thin. Sometimes it is much thicker, as you will notice when working with user interface tests.

As I mentioned in the Goals section, the Page Object Pattern is popular when testing user interfaces, and for good reasons. The pattern is something that Ruibarbo supports and even advocates to use as automation layer.

In short, when using it, you encapsulate UI controls within classes. These classes contain relations to other controls and expose methods that communicate actions you can perform in the application.

It's easiest to illustrate with an example. Let's say we want to enter a street name into a TextBox. When not using the Page Object Pattern, the code might look like this:

TestControl addressControl = UIDriver.FindControlWithName(mainWindow, "CtrlAddress");
TestControl streetNameTextBox = UIDriver.FindControlWithName(addressControl, "TxtStreetName");
Mouse.Click(streetNameTextBox.Left + 2, streetNameTextBox.Top + 2);
Keyboard.Type("221b Baker Street");

As you can see, the intention of the test is totally hidden by implementation details. You probably need a minute or two to understand it. Using the Page Object Pattern, these details can be hidden. The same test might look like this:

mainWindow.Address.StreetName.ChangeTo("221b Baker Street");

Here, mainWindow is an instance of a class MainWindow. It has knowledge about the Address child control, and exposes a property with the relation to it. This property's implementation contains the call to the FindControlWithName() method and returns an instance of the class AddressControl. This class, in turn, has knowledge about the StreetName. StreetName returns an instance of the class TextBox, which in turn exposes a method that can be used to change its contents (Mouse.Click() and Keyboard.Type()).

If you want to, you could even hide the TextBox altogether by changing the interface to the following:

mainWindow.Address.ChangeStreetNameTo("221b Baker Street");

As you can see, the Page Object Pattern makes the code really easy to read. However, it also makes it easy to maintain. Imagine that you decide to change the name of the StreetName TextBox control in the production code. In the first, verbose example, the string "TxtStreetName" is probably found in several tests. Using Page Object Pattern there will only be one place, and not in a test but in the automation layer.

Reading the Getting Started section will help you getting an even better understanding.

Footnotes

[1] The terms outgoing and incoming ports are mine, and are not described by Alistair Cockburn in his Hexagonal Architecture article.

[2] No rule without exception. If you have full control of the state of the external system you could include it in the test. A common example of this is a database that you can for instance clean up before each test. However, you should control the state using the same language as the rest of your test code, the application domain language.

[3] All rules have a context. I'm talking here about a system with a fair amount of complexity. If you are developing Notepad or something equally trivial you probably don't need to follow the ports and adapters architecture. Also, if you are testing a legacy system you might not have any choice but to test the business logic through the user interface. And you can probably come up with more examples of where my rules do not apply.

[4] If you create test doubles for the application, make these double as stupid as possible. Don't include any business logic in them.