System Tests in Django

What is Automated Testing?

It is incredibly beneficial to integrate the habit of writing and developing tests into your software development process. A large part of software engineering is writing and developing tests and test suites to guarantee that the product is stable.

Of course, we’re usually too preoccupied with attempting to develop the actual thing to worry about making sure they work as expected. Or it could be that you’re too arrogant to think it’ll fail.

There are various reasons to incorporate tests, according to the Django Tutorial:

  • A modification in a complicated system can create problems in unexpected areas. Therefore testing will save you time.
  • Tests aren’t simply for identifying problems; they’re also for preventing them: they highlight where the code fails to meet expectations.
  • To make your code more appealing, try the following: “By design, code without tests is broken,” says the author—one of Django’s early developers, Jacob Kaplan-Moss.

Tests facilitate teamwork by ensuring that your team does not mistakenly break your code.

According to the Python Guide, there are a few general rules you should strive to follow while developing automated tests.

The following are some basic guidelines:

  • The focus of the tests should be on a single tiny piece of functionality.
  • Tests should have a clear goal in mind.
  • The tests should be self-contained.
  • Ensure to run tests before you code and before you commit and push your code.
  • Create an even better hook that tests code when it is pushed.
  • For tests, use long and descriptive names.

This article will show you how to write functional tests in Django.

What is System Testing and how does it work?

Selenium tests allow us to run a real web browser, examining how the application works from the user’s perspective.

In summary,

System Tests == Functional Test == Acceptance Test == End-to-end evaluation

The essential thing is that these tests examine how the entire program works from the outside. Because the test has no knowledge of the system’s internals under test, it is referred to as a black box test.

We should be able to follow a human-readable tale in code statements. Further, we make it apparent by including comments with the test code.

We should write the comments first when creating new functional tests to capture the essential aspects of the User Story. In addition, because they are human-readable, you could even share them with non-programmers to discuss the app’s requirements and features.

TDD and agile software development approaches are frequently associated. One of the topics we discuss is the minimal viable app: what is the essential thing we can design that is still useful? So, we usually begin by building an MVP to test the water as soon as feasible.

Getting Ready for the Project

We must first set up a few things before using Python to do functional testing on a Django project. For instance, we need a virtual environment and installing the relevant packages. These can be broken down as follows.

Create a virtual environment that will enable us to install the required packages for creating a Django project setup that will allow us to write functional tests.

Creating a Virtual Environment

Use the following command to create a virtual environment

virtualenv python_m

Subsequently, we will activate it by running the following command.

source python_m/bin/activate

Installing the necessary package

We need to install several python packages to test our functional tests. These include:

  • Selenium
  • Selenium Driver Manager
  • Django

We will use pip to install these packages as follows.

pip install selenium webdriver-manager django

Selenium

This section will explore Selenium and how to write functional tests for our Django projects.

Selenium is a tool for automating browsing the web, which means you can use it to surf the web on real web browsers programmatically.

However, Selenium is mainly used to test web apps.

Selenium includes packages for the majority of programming languages, including Python. As a result, we may use Selenium in Python by installing its package as shown above with other packages or independently.

pip install selenium

For a brief demonstration, we’ll go to the Django Documentation website, do some searching, and look at the first result. Then, we will save and run the script below:

# import the webdriver for Selenium first
from selenium import webdriver
from selenium.webdriver.common.keys import Keys

# instantiate an instance of Firefox
firefox_driver = webdriver.Firefox()

firefox_driver.maximize_window()
firefox_driver.get("https://docs.djangoproject.com/")
input_field = firefox_driver.find_element_by_name("q")
input_field.send_keys("first trial")
input_field.send_keys(Keys.RETURN)
assert "Search" in firefox_driver.title

#  use the CSS selectors to try and find the first result in the page.
actual_results = firefox_driver.find_element_by_css_selector("div#input_field a")
actual_results.click()
assert " first trial " in driver.title.lower()
driver.quit()

The code above is almost self-explanatory. All it does is, look for a search box on the page, type in the search query, then press the return key.

The program then selects the first result and performs an assert.

The Keys class simulates a keyboard and can also be used to enter custom keys. In this demo, we used the RETURN key to submit the form.

Many techniques are available in Selenium to locate an element on a page:

  • find_element_by_id
  • find_element_by_xpath
  • find_element_by_name
  • find_element_by_tag_name
  • find_element_by_css_selector
  • find_element_by_partial_link_text
  • find_element_by_class_name
  • find_element_by_link_text

The first match is returned on the off chance that a match exceeding one is found for a query. Also, note how we used the CSS selector to see the first result in the above example.

Similar methods can be used to find many elements using the same rationale. To find all the links on a page, for example, type:

cumulative_links = firefox_driver.find_elements_by_tag_name("a")

In reality, every single element is a WebElement object with several methods associated with it. The following are the most frequently used:

  • click – performs a mouse click on an element
  • text – returns the element’s text
  • location – returns the element’s location in the renderable canvas
  • _displayed – Determines whether the element is visible to the user.
  • get_attribute(name) – Returns the value of the attribute.

How to create a Django Project

At the core of writing functional tests in Django is the Django project that we can easily create by running the following command.

django-admin startproject Codeconfig

The command above ensures that we successfully create a Django project by the Codeconfig.

Writing the Functional Tests

First, we will create a file called functional_test.py and write the following code in it.

import os
from webdriver_manager.chrome import ChromeDriverManager
import unittest
from selenium import webdriver

class NewVisitorTest(unittest.TestCase):

  def setUp(self):
      # creating temporary directory
      try:
          os.mkdir('temp')
      except FileExistsError:
          pass

      # creating directory to Append Driver
      try:
          os.mkdir('temp/driver')
      except FileExistsError:
          pass

      # initialize the browser
      self.driver = webdriver.Chrome(ChromeDriverManager(path='temp/driver').install())

  def tearDown(self):
      self.driver.quit()

  # the unittest
  def test_start_web(self):
      url: str = 'http://127.0.0.1:8000/'
      self.driver.get(url=url)

      self.assertIn('Todo', self.driver.title)
      self.fail('test Finished')

  if name == 'main':
    unittest.main()

We write a test to see if the website is up and working in this scenario.

The setUp Method

We initialize the driver to connect to our Chrome browser remotely in this technique.

def setUp(self):
    # creating temporary directory
    try:
        os.mkdir('temp')
    except FileExistsError:
        pass

    # creating directory to Append Driver
    try:
        os.mkdir('temp/driver')
    except FileExistsError:
        pass

    # initialize the browser
    self.driver = webdriver.Chrome(ChromeDriverManager(path='temp/driver').install())

The test_start_web method

The test_start_web method is a method to test our web if it functions as intended initially.

def test_start_web(self):
    url: str = 'http://127.0.0.1:8000/'
    self.driver.get(url=url)

    self.assertIn('Todo', self.driver.title)
    self.fail('test Finished')

The tearDown Method

Usually, the tearDown method comes in handy when the test case is complete.

def tearDown(self):
  self.driver.quit()

Running this test will give you a failed response since the title is not equivalent to todo.

Example 1: Creating a User

Now we’ll develop a test in Django admin to create a user. Here, we will create a test.py file with the code below.

from django.test import LiveServerTestCase
from django.contrib.auth.models import User

from selenium import webdriver

class AdminTestCase(LiveServerTestCase):

    def setUp(self):
	"""
             setUp is the entry point for instantiating selenium webdriver.
 In addition, load the browser here.

"""

        User.objects.create_superuser(
            username=administrator,
            password=administrator,
            email='administrator@gmail.com'
        )

        self.firefox_selenium = webdriver.Firefox()
        self.firefox_selenium.maximize_window()
        super(AdminTestCase, self).setUp()

    def tearDown(self):
	"""
         To close the browser, call tearDown
	"""
        self.firefox_selenium.quit()
        super(AdminTestCase, self).tearDown()

    def test_create_user(self):
        """
Create a user test in Django.
In this test, a user will be created in Django admin, 
and the page will be forwarded to the new user change form.
        """
      # Go to the Django administration page.
      # A live server URL attribute is provided by DjangoLiveServerTestCase 
      # that accesses the base URL in tests.
        self.firefox_selenium.get(
            '%s%s' % (self.live_server_url,  "/admin/")
        )

        # Fill in the admin login details.
        username = self.firefox_selenium.find_element_by_id("username_id")
        username.send_keys("admin")
        password = self.firefox_selenium.find_element_by_id("password_id")
        password.send_keys("admin")
        
         # Locate and click the Login button.
        self.firefox_selenium.find_element_by_xpath('//input[@value="Log in"]').click()
        self.firefox_selenium.get( '%s%s' % (self.live_server_url, "/admin/auth/user/add/"))
        # Fill out the form to create a user using your username and password.
        self.firefox_selenium.find_element_by_id("username_id").send_keys("test")
        self.firefox_selenium.find_element_by_id("password1_id").send_keys("test")
        self.firefox_selenium.find_element_by_id("password2_id").send_keys("test")

        # Forms can be submitted immediately by using the submit method.
        self.firefox_selenium.find_element_by_id("user_form").submit()
        self.assertIn("Change user", self.selenium.title)

Django LiveServerTestCase extends the TransactionTestCase class. On setup, it starts a Django server (on port 8081) in the background, and on tearDown, it stops it.

The selenium webdriver is instantiated in the setUp method, and we call quit on it in the tearDown method. In addition, the live server URL attribute is provided by TestCase. Further, it can be used to obtain the server URL.

Execute the test command now, as shown below.

python manage.py test

You’ll see a Firefox browser open, and you’ll be able to run the test. Despite this being a primary sample, you can use the same Selenium concepts to test all of your website’s essential functions.

Example 2: How the User sees the homepage

We’re replicating a user experience in the following functionalities test: how users see the homepage and assert its information.

The user’s story is told at great length in the comments.

from selenium import webdriver
from webdriver_manager.chrome import ChromeDriverManager
import unittest

class NewVisiterTest(unittest.TestCase):
    "User tests the homepage."""
    def setUp(self):
        self.browser = webdriver.Chrome(ChromeDriverManager().install())

    def tearDown(self):
        self.browser.quit()

    def test_homepage(self):
	"""       
 	John has heard about a new internet app that he thinks is cool.
He goes to the website's homepage to see what it's all about.
"""
        self.browser.get('http://localhost:8000')

        # He notices the site name in the page title and header.
        self.assertIn('Everything Python', self.browser.title)
        text_in_header = self.browser.find_element_by_tag_name('h2').text
        self.assertIn('Information on Selenium is coming nicely', text_in_header)

        self.fail('appears when the test on the home page is done!')
 

if __name__ == '__main__':
    unittest.main(warnings='ignore')

setUp() and tearDown() are functions that open and close the webdriver, respectively. In this example, we’re using the Chrome webdriver. But you should be well aware of how to use Firefox, as we have used in the first example above. Also, note that it can be found in your virtual Python environment’s bin path once installed.

As you can see, this is a reasonably straightforward example in which the user double-checks the title and header content.

However, Selenium can also be used to automate form submissions. We’ll examine the login page for our subsequent user narrative to determine if it performs as anticipated.

from selenium import webdriver
from webdriver_manager.chrome import ChromeDriverManager
from selenium.webdriver.common.keys import Keys
import unittest


class LoginTest(unittest.TestCase):
    """User checks whether he can log in on app."""
    def setUp(self):
        self.browser = webdriver.Chrome(ChromeDriverManager().install())

    def tearDown(self):
        self.browser.quit()

    def check_fade_messages(self, msg_text):
        """Check whether text is in returned feedback messages."""
        messages = self.browser.find_elements_by_class_name('msg')
        self.assertIn(msg_text, [msg.text for msg in messages])

    def test_login(self):
        # Max has heard about a cool new online app.
        # He goes to login
        self.browser.get('http://127.0.0.1:8000/login')

        # He notices the site name in the page title and header.
        self.assertIn('Everything Python', self.browser.title)
        form_header = self.browser.find_element_by_tag_name('h2').text
        self.assertIn('Login', form_header)

        # He finds the username input box
        username_inputbox = self.browser.find_element_by_id('user_name')
        self.assertEqual(
            username_inputbox.get_attribute('placeholder'),
            'username'
        )

        # He finds the password input box
        password_inputbox = self.browser.find_element_by_id('user_password')
        self.assertEqual(
            password_inputbox.get_attribute('placeholder'),
            'password'
        )

        # He types account information the input boxes
        username_inputbox.send_keys('fake_username')
        password_inputbox.send_keys('fake_password)

        # He submits the form
        username_inputbox.send_keys(Keys.ENTER)

        #  He sees the error message
        self.check_fade_messages('Invalid reCAPTCHA. Please try again)

        self.fail('Completed login test!')


if __name__ == '__main__':
    unittest.main(warnings='ignore')

We’re utilizing Keys to put in the username and password in the login form. We can’t log in because the app uses Google’s RECAPTCHA to safeguard form submissions. But the good news is that the error message we get is testable.

It’s worth noting that we’re testing constants in our functional tests, such as the headline text or error message text, whereas it’s better to use an element property like id=”recaptcha_error” and validate that instead.

Selenium’s Advanced Features

Back and Forward

To retrieve a webpage, we utilized the “get” method. Selenium also allows us to traverse through the browser’s history.
It’s the same as using the “Back” and “Forward” buttons in a regular browser.

driver.get("https://thirdeyemedia.wpmudev.host/codeunderscored/")
driver.back()
driver.forward()

How to set Cookies

You may also create cookies for domains with Selenium. As a result, when you’re evaluating your local website, this comes in handy in various situations.

# Load a webpage
driver.get("http://localhost:8000")

# Set the cookie
cookie = {"name": "value"}
driver.add_cookie(cookie)

Changing between frames and windows

Selenium includes methods like “switch to window” and “frame switch.” The latter is vital in running commands on either frames or other windows.

# All future commands will now be directed to this frame.
driver.switch_to_frame("the_name_of_frame")

# If a frame doesn't have a name, we can use simple locators to find it.
driver.switch_to_frame(driver.find_elements_by_tag_name("iframe")[0])

# To switch window for the following link:
<a href="/login" target="loginwindow">Login here</a>

driver.switch_to_window("loginwindow")

Chains of Action

Selenium verifies whether an element is visible before performing a click operation on it. It is also to account that a regular user can only click a visible component of a web browser.

However, you may need to linger over an element for it to appear when you click it, like the case with a navigation menu.

Selenium has a class named ActionChains that can do these types of tasks. Here’s an example of code for selecting a submenu from the navigation menu:

# Showing just the essential part of code.
from selenium import webdriver
from selenium.webdriver import ActionChains

driver = webdriver.Firefox()

# get the page

action_chains = ActionChains(driver)
# Locate main menu to be hovered
main_menu = driver.find_element_by_id("drop1")
# Locate sub menu to be clicked
sub_menu = dirver.find_element_by_id("submenu1")

# Action Chains stores all the actions until perform method is called
# Move mouse pointer to main_menu. This will do hover.
action_chains.move_to_element(main_menu)
# Click on the sub-menu
action_chains.click(sub_menu)
# Perform all the commands
action_chains.perform()

ActionChains can also be used to mimic drag-and-drop behavior.

The situation when waiting for Javascript processes

Selenium waits for the website to load and redirects us when we click a link. However, if a website uses ajax calls to populate data on a webpage, we must wait for the data to populate before proceeding.

For this, Selenium supports both implicit and explicit waits:

Implicit waits

Implicit waits instruct the selenium webdriver to wait a set period while searching for an element (s). 0 is the default value.

As a result, if the element is not found immediately, the web driver will throw an exception.

Here’s an illustration:

driver = webdriver.Firefox()
driver.implicitly_wait(6) # value in seconds
# Now all find commands will try for the elements for 6 seconds before raising an exception
driver.get("http://localhost:8000/")
driver.find_element_by_id("sidebar_menu")
# It will find element even if this element is loaded by javascript dynamically

Explicit waits

Before continuing, we can create our code to wait for a condition to be met. Then, the Webdriver will keep checking that condition until satisfied (the default is every 0.5 seconds).

If the sidebar menu cannot be located after 6 seconds, this code will throw a TimeoutException.The Expected Conditions, or EC, are methods we may use to test our applications.

Here are a few examples:

  • invisibility_of_element_located
  • title_contains
  • text_to_be_present_in_element
  • visibility_of_element_located- An element may exist in the DOM but be hidden.

Conclusion

Selenium WebDriver provides a simple API for writing functional and acceptance tests. It simulates a user experience by opening a web browser and reading its content.

Web scraping dynamic websites is another crucial Selenium use case.

With Selenium, you can look at the source code that makes up the webpage and find the elements you’re trying to find, as we have done in most of our examples. Further, it allows us to fill out forms for database operations, logging in users, and register automatically.

It means that a system test or functional test can serve as a kind of application specification. In addition, it tends to monitor what’s known as a User Story, which follows how a user might interact with a feature and how the app should respond.

Meanwhile, you can learn more about Selenium by visiting the Python selenium docs.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *