My other blog (in Hungarian) merhetetlen.blogspot.com

Saturday 18 May 2013

Abstracting abstract abstractions

Even on Dagobah there is no such a thing: enough rain.

Abstractions are good. We can simplify things based on the way how we want to use it. The last time when I was on a QA conference on our one and only Death Star in the Nether sector, there were a whole presentation about how can we create a good framework with creating layers of abstractions.

Lets take an example:
    You have to test a backend user management system, which has a REST API(or SOAP, or json over http or basically whatever). When you have to test it you will create an abstraction for sure which maps your tests to actual http calls somehow. and hopefully you won't make http calls from your test methods every time... This is layer 1.

    After a while, when you get bored to set up the same object over and over again when you want to log in with a test user, or register, or do something you will decide to create another abstraction with util methods for those commonly used actions or group of actions you have to perform over and over again. This is layer 2.

   After a few months or after adding much more tests or after covering many many new features you realize even with these util methods sometimes it is painful (in terms of lines of code, or in terms of readability / maintainability) to set up complex preconditions. So you decide you need another layer. But it is not obvious what.

    Maybe you already have a class file which represents your test users. So it can hold basic or not basic info like: first name, last name, dob, email, password, number of dogs, IQ etc... And maybe you will start to wonder: if I have a list (or something like a list) of attributes what a user can have, why do not I have a list of actions what a user can perform, or can take. You can have things like: login, register, send message to someone, get banned, fly to the moon, get fired, get attacked by Jedis etc...
    One of the best way to list things in java is to create an enum. Another benefit is, you can define an abstract method, what every enum must implement, something like: doAction(TestUser). So in you enum thing, you can list your actions, and implement the required api calls to achieve that action (use things from previous layer). And in your test user object you can do a method like: doActions(MyOwnActions... action) and you can list those things what you want to perform with your test user. This is layer 3. and it is really handy:
user.doActions(REGISTER, LOGIN, GO_TO_MOON, KILL_A_JEDI, EAT_BAKLAVA);

    So we have 3 layers for a simple api testing which abstracts the http calls to a level where you can just list what you want to do and your test user will be in that state. But what happens if someone testing the GUI for example needs your test user thingy. You can tell him to do what you did, so before the tests create a test user instance and set it up with the required actions and so on.
It means they have to change or have the same kind of things on multiple place (of course depends on, maybe you have to keep your test class hierarchy flat, and you do not need users in every test). So maybe in that framework it can be ugly to set up the test users everywhere, and you want to provide a nice way to just get what you want.
    You already have a static abstraction about the actions what your test users can perform, so you can easily create dependency injections.
    Well I know that this example is not that common, but in my case we cannot just inject the dependency with guice or something... here, in the tests the test user depends on runtime parameters, and you need a given and often various state of the user.
    What can you do? You can create an annotation like: @TUser(actions={Action1, Action3}) and if you use testng you can override the IMethodInterceptor so you can create a test user into the annotated field based on some parameters (from the test instance, or runtime things) you decide to use. So you have abstracted the test user initialization, and this is layer 4.

...well, when I implemented this I thought there are at least 8 level of abstractions .. it's only 4, but the message is clear, if things are getting complicated and you have lot of duplication do not afraid to take a step back, look it from a new point of view, and put those things what you need to another layer.

...sorry for the half broken language, my brain is half off....
 

No comments:

Post a Comment