We started to use selenium tests almost 2 years ago and since that moment we changed our approach twice. At the very beginning we had only one person involved in that process, we had a lot of "copy/paste", because all tests are more like a complex scenarios with some prerequisites and dependencies. So there were obviously a need to refactor everything and make code more clear and maintainable. I definitely should mention that each test had over 60 lines of code and two test usually had around 80% of duplicated code that means we had a lot of "copy/paste". The first and most obvious way to fix all these shit was to create a base class and put there all common logic. Each logical block in a separate method. So that we got rid of global "copy/paste" between test and achieved the reusable parts.
Life became easier but there still were a room for improvements. We wanted to have tests writen in a such way that even a not technical person could read it. We wanted some kind of a DSL. And then we tried a SpecFlow. It looked very cool and we were encourage to try it. But it turned out to be that SpecFlow did not feet our needs due to a couple things:
Actually not bad...but there are some issues which such approach does not solve. In order to make it clear I warn that curly braces after LogincAsCustomer does nothing except group methods bellow in one logical context. Login method has two implementations: LoginsAsCustomer and LoginAsManager. Let's imaging the situation when we want to write a scenario with two customers, obviously we will create another method with ugly name LoginAsCustomer2 or LoginAsSecondCustomer and so on in case we need three or four customers simultaneously. Or another example...now we have step ApproveDelivery for customer, but we might want to have an ability to approve delivery on behalf of a customer, it means that our manager will need another method with slightly different name ApproveDeliveryOnBehalf or something very similar. But what we really wanted it some context specific approach. Ability to use the same method login that is aware about which credentials to use.
I'm totally into DI and loosely coupled code, I want to have small and well described steps for my tests. Let's look how should look simple step, as an example I will show Login step.
For demo purpose this code doesn't interact with database or some other services, it only logs in console user credentials. It has Execute method from IExecutabeStep, each step in our scenario should be executed :) so all steps should implement this scenario. And it also has a BaseStep as ancestor we will look later at BaseStep more deeply. Login step should know a credentials to use. It means that our login step has a dependency and we need a way to solve it some how. And the most interesting thing that we can resolve it differently according to execution context. And this is the time to show what is execution context.
My implementation is based on Ninject, it's open source IoC framework. Those who don't know what is Binding probably should get famiilar with Ninject or some other IoC container. Actually it's not so complicated. IKernel it's an interface that has very useful method Get which accept a type. Let's say we want to create an instance of Login step, then we will call
very simple, the thing is that by itself Login type is a public class and could be easily instantiated, but there is one issue. We have a dependency on UserCredentials and we need to know where to get this credentials. Here comes another method of kernel, it's method Bind. Most likely you would do something like this:
Here we say that every time we want instance of ISomeInterface we should create an instance of SomeImplementation which should be obviously derived from ISomeInterface. In our case we want to have some user credential aware context, just like this one.
Here we say that we want to resolve UserCredential by some specific object with some specific login and password. We can create two or more such contexts each with unique credentials, one for customer another for manager and so on, let take a look how to do it:
It's a factory that knows how to create a context. So I can use Users.Customer(); and it will return we a customer context or I can use Users.Manager(); and it will return me a manager context.
Now it's time to get everything together, our code will look like:
Let's talk about As and Do methods. As creates execution scope, it accepts IExecutionContext as parameter, a collection of IExecutionContext if to be more precised. And register all dependencies using AddContextBindings method. Then we call method Do which resolves step instance using all bindings declared in As method and then call Execute method of that step. Before to leave execution scope we need to remove all bindings that were declared at the beginning, at this point we need a method RemoveContextBindings of the interface IExecutionContext.
I almost forgot about BaseStep, this is how it looks.
Logger is marked with Inject attribute, it's a ninject attribute, that allow to mark a public property as a dependency that has to be resolved.
Source code and another examples are available on the bitbucket.
Later I'm going to show how to use it together with Selenium WebDriver, because initially this approach was designed to maintain complex scenarios above selenium.
Source code
Life became easier but there still were a room for improvements. We wanted to have tests writen in a such way that even a not technical person could read it. We wanted some kind of a DSL. And then we tried a SpecFlow. It looked very cool and we were encourage to try it. But it turned out to be that SpecFlow did not feet our needs due to a couple things:
- It's built over regular expressions to match scenario and real code, but has no intellisense and no tools that prevent misspelling
- In some cases we have really complex setup and it's really hard, I'd rather say impossible, to describe it in SpecFlow way.
- Base class with protected methods like: Login, Logout, GoToPage, OpenXxxWindow etc.
- Helper classes to work with cookies, with Selenium Web driver etc.
[TestFixture] public class customer_should_be_able_to_order_a_book : BaseTest { [Test] public void customer_should_approve_delivery_after_successfull_shippment() { LoginAsCustomer(); { OpenBookStoreWebPage(); FindBook(); AddToTheBasket(); CheckOutTheOrder(); } LoginAsManager(); { GoToOrders(); FindAnOrder(); ConfirmShippmnet(); } LoginAsCustomer(); { GoToMyOrders(); ApproveDelivery(); } } }
Actually not bad...but there are some issues which such approach does not solve. In order to make it clear I warn that curly braces after LogincAsCustomer does nothing except group methods bellow in one logical context. Login method has two implementations: LoginsAsCustomer and LoginAsManager. Let's imaging the situation when we want to write a scenario with two customers, obviously we will create another method with ugly name LoginAsCustomer2 or LoginAsSecondCustomer and so on in case we need three or four customers simultaneously. Or another example...now we have step ApproveDelivery for customer, but we might want to have an ability to approve delivery on behalf of a customer, it means that our manager will need another method with slightly different name ApproveDeliveryOnBehalf or something very similar. But what we really wanted it some context specific approach. Ability to use the same method login that is aware about which credentials to use.
I'm totally into DI and loosely coupled code, I want to have small and well described steps for my tests. Let's look how should look simple step, as an example I will show Login step.
public class Login : BaseStep, IExecutableStep { public UserCredentials _userCredentials; public Login(UserCredentials userCredentials) { _userCredentials = userCredentials; } public void Execute() { //fake implementation Logger.Log("Login: {0}", _userCredentials.Login); Logger.Log("Password: {0}", _userCredentials.Password); } }
For demo purpose this code doesn't interact with database or some other services, it only logs in console user credentials. It has Execute method from IExecutabeStep, each step in our scenario should be executed :) so all steps should implement this scenario. And it also has a BaseStep as ancestor we will look later at BaseStep more deeply. Login step should know a credentials to use. It means that our login step has a dependency and we need a way to solve it some how. And the most interesting thing that we can resolve it differently according to execution context. And this is the time to show what is execution context.
public interface IExecutionContext { void AddContextBindings(IKernel kernel); void RemoveContextBindings(IKernel kernel); }
My implementation is based on Ninject, it's open source IoC framework. Those who don't know what is Binding probably should get famiilar with Ninject or some other IoC container. Actually it's not so complicated. IKernel it's an interface that has very useful method Get which accept a type. Let's say we want to create an instance of Login step, then we will call
kernel.Get<Login>();
very simple, the thing is that by itself Login type is a public class and could be easily instantiated, but there is one issue. We have a dependency on UserCredentials and we need to know where to get this credentials. Here comes another method of kernel, it's method Bind. Most likely you would do something like this:
kernel.Bind<ISomeInterface>().To<SomeImplementation>();
Here we say that every time we want instance of ISomeInterface we should create an instance of SomeImplementation which should be obviously derived from ISomeInterface. In our case we want to have some user credential aware context, just like this one.
public class UserAwareContext : IExecutionContext { private readonly UserCredentials _userCredentials; public UserAwareContext(UserCredentials userCredentials) { _userCredentials = userCredentials; } public void AddContextBindings(IKernel kernel) { kernel.Bind<UserCredentials>().ToConstant(_userCredentials); } public void RemoveContextBindings(IKernel kernel) { kernel.Unbind<UserCredentials>(); } }
Here we say that we want to resolve UserCredential by some specific object with some specific login and password. We can create two or more such contexts each with unique credentials, one for customer another for manager and so on, let take a look how to do it:
public class Users { public static UserAwareContext Customer() { return new UserAwareContext(new UserCredentials { Login = "john", Password = "orange" }); } public static UserAwareContext Manager() { return new UserAwareContext(new UserCredentials { Login = "manager", Password = "cherry" }); } }
It's a factory that knows how to create a context. So I can use Users.Customer(); and it will return we a customer context or I can use Users.Manager(); and it will return me a manager context.
Now it's time to get everything together, our code will look like:
var customer = Users.Customer();
As(customer).Execute(() =>
{
Do<Login>();
}
Let's talk about As and Do methods. As creates execution scope, it accepts IExecutionContext as parameter, a collection of IExecutionContext if to be more precised. And register all dependencies using AddContextBindings method. Then we call method Do which resolves step instance using all bindings declared in As method and then call Execute method of that step. Before to leave execution scope we need to remove all bindings that were declared at the beginning, at this point we need a method RemoveContextBindings of the interface IExecutionContext.
I almost forgot about BaseStep, this is how it looks.
public class BaseStep { [Inject] public Logger Logger { get; set; } }
Logger is marked with Inject attribute, it's a ninject attribute, that allow to mark a public property as a dependency that has to be resolved.
Source code and another examples are available on the bitbucket.
Later I'm going to show how to use it together with Selenium WebDriver, because initially this approach was designed to maintain complex scenarios above selenium.
Source code