2

OOP Principles in Test Automation: Abstraction

 2 years ago
source link: https://blog.testproject.io/2022/03/01/oop-principles-in-test-automation-abstraction/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

We reached the final subject in this series. This time we’re talking about abstraction in OOP and in test automation. This one is a bit more abstract, and you may not always find the need to use it. But we should at least understand it, so let’s dive in 🤿

Table of Contents

Abstraction definition

In object-oriented programming, abstraction is the process of moving the focus from the concrete implementation of things to the types of things (i.e. classes), and their available operations (i.e. methods).

In C#, abstraction can be achieved through abstract classes and interfaces. The main difference between the two is that abstract classes allow concrete implementation, while interfaces don’t ❌

Abstract classes

Here are the particularities of abstract classes:

  • They are declared using the abstract modifier.
  • Abstract classes cannot be instantiated.
  • Abstract methods can only be declared inside abstract classes.
  • The abstract methods provide no implementation.
  • The non-abstract classes derived from an abstract class must provide implementations for all the abstract methods inherited from the base class.
  • The concrete implementation in the derived classes must use the override modifier (see the polymorphism article for more details on overriding methods).
  • You can also have concrete (non-abstract methods) in abstract classes.

Abstract class example

We’ll use the same example as before – the Animal class. Only this time, we’ll declare the class and the Eat() method as abstract:

abstract class Animal
{
   public abstract void Eat();
   public void Sleep() => Console.WriteLine("Sleeping"); 
}

This means that any class that’s derived from the Animal class will need to have an implementation of the Eat() method. For example:

class Cat : Animal
{
   public override void Eat() => Console.WriteLine("Eating tuna");
}

class Dog : Animal
{
   public override void Eat() => Console.WriteLine("Eating chicken");
}

If the implementation is not provided, let’s say in the Dog class, the build will fail:

Now we can create new instances of the Dog and Cat classes, and use the methods in the inherited class:

Dog dog = new Dog();
Cat cat = new Cat();

dog.Sleep();
dog.Eat();

cat.Sleep();
cat.Eat();

This time, the output will look like this:

Interfaces

You can think of interfaces like you would a template:

  • They are declared using the interface keyword.
  • Naming conventions in C# state that interface names should start with the letter I (e.g.: IAnimal).
  • Interfaces cannot have any concrete implementations.
  • Classes derived from an interface must provide implementations for all the methods of the interface.
  • Interface members cannot have access modifiers.
  • You can read more on interfaces in the Microsoft Documentation.

Interface example

Let’s see how the above example would look like using an interface instead of an abstract class 💡 First, let’s create the interface:

interface IAnimal
{
   void Eat();
}

You can see that we’re using the interface keyword, and that the Eat() method has no access modifier and no implementation.

Just like before, if we inherit the interface in a class, the class must have an implementation for the Eat() method, or else the code won’t compile. We don’t need to use the override modifier this time in the derived class. Everything else is pretty much the same.

class Cat : IAnimal
{
   public void Eat() => Console.WriteLine("Eating tuna");
}

Using abstraction in test automation

Just like before, we can have a base page class, except this time it can be abstract ✅ This means that it can contain some abstract methods. The thing is, if we choose to use it like this, we must implement these methods in all the derived page classes.

Abstraction example in test automation

It can look something like this:

public abstract class BasePage
{
   protected readonly IWebDriver driver;

   public BasePage(IWebDriver driver)
   {
      this.driver = driver;
   }

   private IWebElement DashboardMenuLink => driver.FindElement(By.Id("menu_dashboard_index"));

   public DashboardPage OpenDashboard()
   {
      DashboardMenuLink.Click();
      return new DashboardPage(driver);
   }

   public abstract bool IsTitleCorrect();
}

Like we said before, the class can have both concrete and abstract implementation. But this time, we won’t be able to create new instances of the BasePage class, only of its derived classes. And we must implement the IsTitleCorrect() method in all derived classes. For example:

class AdminPage : BasePage
{
   public AdminPage(IWebDriver driver) : base(driver) { } 

   private IWebElement LoggedUser => driver.FindElement(By.Id("welcome"));

   public bool IsUserLoggedIn(string username)
   {
      return LoggedUser.Text.Contains(username);
   } 

   public override bool IsTitleCorrect()
   {
     return driver.Title.Equals("OrangeHRM");
   }
}

Conclusion

There are pros and cons to using interfaces vs abstract classes, which we did not cover in this article. The truth is, abstraction may add too much complexity in a test automation project. For example, you may not always want to have the same method implemented in different ways in derived classes.

In this case, using just inheritance and providing the concrete implementation in the base class might be a better idea. So before you use any of the covered principles in your project, make sure that you’re not just using them for the sake of doing OOP, but rather because they actually provide value.

I hope you enjoyed my OOP series! Let me know if you have any questions 💫


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK