This month we are continuing our investigation of SOLID principles with the “O” value: the Open-Closed principle. This principle states the following: a class should be open for extension, but closed for modification.
What does this mean? It means that once a class is used by other code, you shouldn’t change the class. If you do change the class, you risk breaking the code that depends on the class. Instead, you should extend the class to add functionality.
Let’s see what this looks like with an example. We’ll use a Login class again, because software testers encounter login pages so frequently. Imagine that there’s a company with a number of different teams that all need to write UI test automation for their features. One of their test engineers, Joe, creates a Login class that anyone can use. It takes a username and password as variables and uses them to complete the login:
class Login {
constructor(username, password) {
this.username = username
this.password = password
}
login() {
driver.findElement(By.id('username'))
.sendKeys(this.username)
driver.findElement(By.id('password))
.sendKeys(this.password)
driver.findElement(By.id('submit)).click()
}
}
Everybody sees that this class is useful, so they call it for their own tests.
Now imagine that a new feature has been added to the site, where customers can opt to include a challenge question in their login process. Joe wants to add the capability to handle this new feature:
class Login {
constructor(username, password, answer) {
this.username = username
this.password = password
}
login() {
driver.findElement(By.id('username'))
.sendKeys(this.username)
driver.findElement(By.id('password'))
.sendKeys(this.password)
driver.findElement(By.id('submit')).click()
}
loginWithChallenge() {
driver.findElement(By.id('username'))
.sendKeys(this.username)
driver.findElement(By.id('password'))
.sendKeys(this.password)
driver.findElement(By.id('submit')).click()
driver.findElement(By.id('answer'))
.sendKeys(this.answer)
driver.findElement(By.id('submitAnswer')).click()
}
}
Notice that the Login class is now expecting a third parameter: an answer variable. If Joe makes this change, it will break everyone’s tests, because they aren’t currently including that variable when they create an instance of the Login class. Joe won’t be very popular with the other testers now!
Instead, Joe should create a new class called LoginWithChallenge that extends the Login class, leaving the Login class unchanged:
class LoginWithChallenge extends Login {
constructor(username, password, answer) {
super()
this.username = username
this.password = password
this.answer = answer
}
loginWithChallenge() {
this.login(username, password)
driver.findElement(By.id('answer'))
.sendKeys(this.answer)
driver.findElement(By.id('submitAnswer')).click()
}
}
Now the testers can continue to call the Login class without issues. And when they are ready to update their tests to use the new challenge question functionality, they can modify their tests to call the LoginWithChallenge class instead. The Login class was open to being extended but it was closed for modification.
Great series, thanks for posting about these concepts to help testers improve their automation code.
I have a few questions about the example, I’m not sure what purpose extending the Login class serves for the new LoginWithChallenge class, it seems like unnecessary coupling?
There also seems to be some code duplication with the initial steps of logging in. Perhaps it could be somehow shared between the two classes so that LoginWithChallenge can re-use the logic and simply add-on to it, instead of re-implementation. Login is a simple task but I imagine with more complex ones the engineer wouldn’t want to copy paste everything like in the example provided?
Hi Victor- Thanks for your questions. You are totally right that there is unnecessary coupling in this example. The point of my example (and the examples I will give in my future posts) is to make understanding the concept as simple as possible. My hope is that testers who understand the principle will be able to notice if they are violating it when they write their tests.
Sorry to say that but this article is totally wrong about what that principle is about. This kind of example is really useless tbh. Just imagine what happens when the next option to login is provided? Are you going to extend that that newely class that you just created with even a newer one? It is going to create some ridiculous chain of extending and that will be unmaintainable…
Instead the original LopinPage class should be modified with the new method without this unneccessary extending.
OCP is really not applicable in the example that you provided.