I believe that testing is a crucial part of software development. Some might might argue that it's not that important, but I think it is. But, that's a discussion for another time.
If you're still reading, it means that the previous paragraph didn't throw your attention to other tabs in your browser. Good! Let's dive right in and check out what is this PageController thing.
Unit testing, again in my opinion, is a fundamental practice to any software development process. But, it has it's shortcomings. Like the name hints, it deals with units of code. When we have a system, for example a web application, we need that those units not only to work but work together. Writing integration tests for web applications can be somewhat of a pain.
Testing this kind of stuff, although important, can be boring and very often neglected. How can we make it more easy and useful? Let's look at the following example, that uses Selenium and Python unittets.
class TestTutorial(unittest.TestCase):def test_tutorial(self):# Create a new instance of the Firefox driverdriver = webdriver.Firefox()# Go to google homepagedriver.get('http://www.google.com')# Find the element that's name attrbut is q (the google search box)inputElement = driver.find_element_by_name('q')# Type in the searchinputElement.send_keys('cheese!')# Submit the form (altough google automatically searches without submitting)inputElement.submit()try:# We have to wait for the page to refresh, the last thing that seems to be updated is the titleWebDriverWait(driver, 10).until(EC.title_contains('cheese!'))# It should be see 'chesse!' - Google Searchself.assertEqual(driver.title, 'cheese! - Google Search')finally:driver.quit()
Here we have a simple example, but one thing that jumps right at us is the fact that navigating the page is mixed with the test itself. Hence, PageController.
The idea is very simple: we separate the actual test from navigating the page. In fact, our actual test will not know anything about the page itself. Let's see how we can achieve that.
class PageController():def __init__(self):# Create a new instance of the Firefox driverself.driver = Nonedef init_firefox_driver(self):self.driver = webdriver.Firefox()def insertQuery(self, text):self.init_firefox_driver()# Go to google homepageself.driver.get('http://www.google.com')# Find the element that's name attrbut is q (the google search box)inputElement = self.driver.find_element_by_name('q')# Type in the searchinputElement.send_keys('cheese!')# Submit the form (altough google automatically searches without submitting)inputElement.submit()try:# We have to wait for the page to refresh, the last thing that seems to be updated is the titleWebDriverWait(self.driver, 10).until(EC.title_contains('cheese!'))# It should be see 'chesse!' - Google Searchreturn self.driver.titlefinally:self.driver.quit()class TestTutorial(unittest.TestCase):def __init__(self, *args, **kwargs):super(TestTutorial, self).__init__(*args, **kwargs)self.pageController = PageController()def test_tutorial(self):page_title = self.pageController.insertQuery('cheese!')self.assertEqual(page_title, 'cheese! - Google Search')
More code, hun? But isn't it more descriptive? Imagine a QA Engineer, that does not necessarily know how to code, trying to follow a complex example. It would be a lot harder. The same guy, looking at our TestTutorial can follow what's going on. He does not want to know any specifics about the code but needs to understand what's going on.
What does that mean for us developers? This means more abstraction and a more comprehensive way of defining tests. Each page can be abstracted into it's own controller. The tests can be written in their own classes. If the page changes, we only need deal with the navigation part but the actual test will remain intact.
P.S. For extended details on this I recommend reading this excellent post from Hedley Proctor: Effective Selenium testing.