-
Notifications
You must be signed in to change notification settings - Fork 4
Architecture of MrChecker Modules
MrChecker source code is currently composed of 6 different modules. Core Module, Example Module and 4 Functional Modules enabling framework users to build and perform tests for different types of domains e.g. Selenium or Web API.
Core Module: provides common features to all functional modules. Manages other modules. For instance it allows to control in one place:
-
Parallelization of tests and test execution (e.g. JUnit management)
-
Logging
-
Loading properties, environment and runtime parameters.
Core Module does not have strong dependency to functional modules which allows for greater modularity and ease of maintenance.
Functional Modules: focus on delivering functionalities that are specific to the domain of tests e.g. managing WebDrivers for Selenium, methods to interact with Database Objects or Virtualization for services for WebAPI module. They plug into Core Modules as adapters to make use of common functionalities delivered by Core Module e.g. logging.
Example Module: is a template for creating new functional modules. It contains all necessary configuration to instantiate a module for a new domain. e.g. It could be used to extend MrChecker framework with support for Mobile Testing.
Architecture of MrChecker make use of the following 3 principal design patterns or techniques:
-
Observer pattern
-
Page Object Model pattern
-
Dependency Injection
Application of Observer pattern between Functional and Core Modules allows for loose coupling of classes and weak intra-module dependencies.
In other words Core Module does not need to know which specific module test is run for. When a new module is created it needs to define method to register as observer and implement required methods that are called from Core Module (observed). Core Module provides an interface for modules to plug-in as adapters. All this makes it simple to extend MrChecker with new functionalities implemented in new modules.
-
Each module must contain module specific BasePage that has addObserver() method used to register itself at Core Module (BaseTestWatcher)as Observer of tests results.
-
Each module BasePage must implement ITestObserver interface. That will assure all methods that can be called back on observing objects by Core Module (Observed object) are there. This is how observer pattern is implemented.
-
Each test class should extend BaseTest.Thanks to that test control will be redirected to ParallelTestClassRunner and BaseTestWatcher will replace default JUnit test watcher.
The following shows what happens step by step in the framework when a test is executed.
-
Test is run from a test class that extends BaseTest from Core Module. BaseTest class defines that BaseTestWatcher should be used to take action on test events, without modyfying them. If something happens to test now e.g. it fails, then BaseTestWatcher will notify all observing objects (Modules) accordingly.
-
Test performs actions on testing object ("Page"). We can have more than one Page Object type involved. For instance test can operate on API "Page" and "Selenium" page at the same time. API "Page" would represent and endpoint, and Selenium "Page" a web page. So in MrChecker many different modules can be involved at the same time.
-
Page Object extends BasePage from the Module.
-
Module BasePage implements a number of methods that will finally make "link" to Core Module complete.
-
When Page instance is created during test it adds Module to list of Observers for the running test.
-
BaseTestWatcher listens to test events. It replaces generic Junit TestWatcher.
-
BaseTestWatcher invokes @Overriden methods on all Modules involved in test.
-
This way all involved Pages will be able to act on test failure or success independently e.g. DB "Page" can close connection and Selenium "Page" can have screenshot taken. On test success all can log in a message in the log.
Example Module contains all necessary classes that can be copied and adjusted to construct fully the above interaction pattern.
Page Object Pattern/Page Object Model is a design pattern where organization and execution of tests follows the principle of responsibility separation. One entity is responsible for describing tested object and actions that can be run against it. Another one is responsible for executing test using methods provided by tested object ("Page"). Among many other benefits POM in testing greatly reduces cost of test framework maintenance. Even though the concept emerged from Selenium/Web testing in MrChecker it is extended across all Functional Modules.
Driver is an important addition making use of POM possible. In simple words it allows to perform actions on Pages. Driver examples are Selenium WebDriver for Selenium Module or RestAssure for API Module or JDBCDriver for DB Module. Eaach module should implement getDriver() method in BasePage so all child pages can interact wth Object Under Test. Test classes should not use Driver directly, instead they should be calling Page methods that do. So, the Page class' methods should provide all functionalities that are needed in the Test (and may add new) wrapping and extending methods that are available in the Driver.
Dependency Injection is a technique used in software development to promote programming by interfaces and creating weak dependencies between classes. It reduces cost of maintenance because dependencies can easily be changed in one place (when injection is realized) rather than in all the places where the class is used. MrChecker uses Guice library to implement Dependency Injection. Dependency Injection follow Inversion of Control pattern (IoC). Usually DI is two-step approach. In first step binding of dependencies must be configured. In second step, dependencies are resolved.
Examples of use of DI in MrChecker:
1) Properties injections in setter
//binding @Singleton public class PropertiesSettingsModule extends AbstractModule { ... Propertiesproperties=loadProperties(path); Names.bindProperties(binder(),properties); }
//resolving Public class PropertiesFileSettings{ ... @Inject(optional=true) private void setProperty_1(@Named("name.property_1")Stringpath) ... }
2) Using Provider (@Provides) to satisfy injection of Environmental variables
//binding public class EnvironmentModule extends AbstractModule{ ... @Provides IEnvironmentService provideSpreadsheetEnvironmentService(){ ... return SpreadsheetEnvironmentService.getInstance(); } ... }
//resolving public abstract class BaseTest implements IBaseTest{ ... IEnvironmentServiceenvironmentInstance=Guice.createInjector(newEnvironmentModule()).getInstance(IEnvironmentService.class); ... }
Parameter types
Core Module facilitates management of 3 existing type of parameters that can be used in MrChecker:
-
Properties (1 and 2) : each module has its own property file, named settings.properties. Properties are used to toggle ON/OFF entire features or behaviours. For instance in Selenium Module one can disable driver auto-update by specifying: selenium.driverAutoUpdate=true. When creating new module developer should make corresponding changes in the following module files:
Then developer can use properties in named injections. That is possible because class binding occures in setPropertiesSettings() method in BasePage.
-
Environment variables (3) : are defined in environments.csv file. As name suggests environment.csv file should contain elements that have different values based on environment against which tests are run. e.g. URL for an API end-point can be a good example here. Management of environment variables happens in the Core Module through the use of core.base.environment.EnvironmentModule class which is a provider for DependencyInjection mechanism described above. The SpreadsheetEnvironmentService class will use environments.csv file from module from which injector is requested.
-
Run-time parameters: can be specified dynamically when tests are run and correspond to maven -D*parameter1*, -D*parameter2*,… variables. An example use of run-time parameter is "mvn clean test -D*browser*=firefox". They are used to run tests in different configurations.
Example - Selenium Module runtime parameters
Initialization of parameters
In BasePage static block is used to initiate paramaters. Actions contained in that block are only executed once in an entire lifecycle of application.
static{ //GetanalyticsinstancecreatedinBaseTets analytics=BaseTest.getAnalytics(); //Getandthensetpropertiesinformationfromselenium.settingsfile propertiesFileSettings=setPropertiesSettings(); //ReadSystemormavenparameters setRuntimeParametersSelenium(); //ReadEnvironmentvariableseitherfromenvironmnets.csvoranyotherinputdata. setEnvironmetInstance(); }
Encoding passwords
In resources one more config file can be found - secretData. It holds a key that is used to encode and decode passwords. Encoded passwords can be then stored in environments.csv. Since secretData holds a secret it should be added to .gitignore. Developers and CI tools such as Jenkins should have it locally installed (e.g. Jenkins can use Credentials Binding plugin to use withCredentials code block).
Example Module
Let us re-cap on what one can find in example module and what files need to be adjusted when a new module is created.
(1) BasePage - implements method that will be called on important test events such as failure or success. Initialization of parameters happens here before class is loaded. In BasePage we will find registration of Module as Test Observer and getDriver method. All Module Pages will have to extend BasePage
(2) All tests must extend BaseTest from Core Module. Core Module BaseTest class registers test watcher that will notify Module on test events (it will call methods from Module BasePage)
(3), (4) properties need to be defined in these two files. Dependency Injection is used to set properties values based on the settings.properties file.
(5) environments.csv holds environment variables, Core Module handles initiation of this parameters
(6) secretData contains a secret used to encrypt and decrypt sensitive data, it should never be distributed with code
(7) here runtime parameters are managed, one specifies them with -D maven switch
(8) DriverManager handles driver creation