CITS5501 lab 8 (week 9) – system testing

Reading

It is strongly suggested you complete the recommended readings for previous weeks before attempting this lab/workshop.

A. Sample web app

The Git repository at https://github.com/cits5501/week-9-lab-system-testing contains a very simple sample web application. If you’re not familiar with how web servers and web apps work, this article provides a brief guide.

You can use GitPod to work with this code online. You will need a GitHub account; once you have one set up, follow the link below:

  https://gitpod.io/new/#https://github.com/cits5501/week-9-lab-system-testing

You can find further information on working with GitPod in the lab 1 worksheet.

Alternatively, if you’re familiar with using Git and Java from the command line, you can download a copy of the source code using the command

git clone https://github.com/cits5501/week-9-lab-system-testing

This worksheet will assume you’re using GitPod to access the repository. GitPod provides you with access to the Visual Studio Code (VS Code) editor. Once VS Code starts, it will take a few seconds to recognize the repository as containing Java source code and to analyse it; once it does, the project explorer should show expandable tabs entitled “JAVA PROJECTS” and “OUTLINE”.

Spend some time looking at the source code for the web app. The web app makes use of Flak, a simple web framework inspired by Python’s Flask framework.

Some of the major components of the web app are:

The app instance variable.

The App interface is provided by Flak; you can see the source code for it here. It allows the web app to be started and stopped (using the start() and stop() methods), and also allows code within the web app to obtain information about the server it’s running on (getServer()) and the web request currently being processed (getRequest()).

Route handlers.

The URLs a user can access within a website are referred to as routes, and when a user tries to access a particular URL, the Flak framework processes it using a particular route handler.

Here is an example route handler for the /login route:

  @Route("/login")
  public String login() throws IOException {
    Path filePath = Path.of("resources/login.html");
    String contents = Files.readString(filePath);
    return contents;
  }

The @Route annotation tells Flak what route this is the handler for. The code within the handler is responsible for returning the contents of the web page (here, as a String, though other formats are also possible).

Different routes handle the case where a web page is being viewed (as in the case above), versus when data is being sent by the browser to the server. For an example of the latter, see the code that starts:

  /**
   * process a "/login" form submission
   */
  @Post
  @Route("/login")
  public void login(Response r, Form form, SessionManager sessionManager) {
    // ...

The @Post annotation here says that Flak should only invoke this handler when data (in this case, the contents of a login form) is being sent to the web server by a browser using what’s called a “POST request”.

main() method

The VeryBasicWebApp class has a main() method which can be used to start the server running, listening for requests on a particular port.

In VS Code, you should be able to start the web app running by right-clicking on the “VeryBasicWebApp.java” file in the VS Code explorer, and selecting “Run Java”.

Launching the app

You can also launch the web app from the command line.

If the Java code for the web app has been compiled into .class files contained in the directory “bin”, then the following will launch the app:

$ java -cp 'lib/*':.:bin VeryBasicWebApp

A notification will appear at the bottom right of the editor similar to the following:

Click the “Open Browser” button, and a new browser tab should open, displaying the content “To log in, visit the login page”.

If you visit the “login” page, enter a username and password, and submit the form, the following method is the one that handles the attempted login:

  /**
   * process a "/login" form submission
   */
  @Post
  @Route("/login")
  public void login(Response r, Form form, SessionManager sessionManager) {
    // just for debugging
    System.err.println("form params were:" + form.parameters());
    
    String username = form.get("username");
    String password = form.get("password");

    if (username.equals("foo") && password.equals("bar")) {
      SessionManager sm = sessionManager;
      FlakUser user = sm.createUser(username);
      sm.openSession(app, user, r);
      r.redirect("/app");
    }
    else
      r.redirect("/login");
  }

Key aspects of this code are:

B. Load testing

Suppose we want to perform load testing of our web app – ensuring that it can handle the expected load of web requests. How can we do this?

It’s possible to access our web app from the command line, using Unix utilities such as cURL. In VS Code, with the server still running, open a new Bash terminal, by clicking on the “+” sign at the lower right of the window, and selecting “Bash”.

Within the terminal, type

  curl -w '%{time_connect}:%{time_starttransfer}:%{time_total}\n' http://localhost:8080/login

cURL will download the page you requested, display the page content, and display some statistics about how much data was transferred (with colons as separators) – the time taken to set up a connection to the server (time_connect), the time at which cURL starts transferring data from the server (time_starttransfer), and the total time spent retrieving the page (time_total).

We could do very simple load testing by making many requests using cURL in a short period of time against our web app, and measuring the time taken. (In reality: we would likely want the web app to be running on a different computer from the one where we do tests, so that the process of running tests doesn’t interfere with the running of the web app.)

A load test would follow the same ‘Arrange, Act, Assert’ pattern we have seen for other sorts of test:

Arrange

Start the web app running

Act

Make requests against the web app, and measure the time taken to complete them.

Assert

Assert that the time taken meets whatever criterion we have specified.

We could write our tests using JUnit if we wished (perhaps using the java.lang.Process API to launch cURL, or perhaps making and timing web requests using Java libraries), or we could write our tests in a scripting language such as Bash or Python.

More typically, however, we would use dedicated load-testing software. An example of this is Apache JMeter, which can be used to test the performance of a wide range of systems – not just web applications, but also systems that act as (for instance) mail servers or authentication servers. As an exercise in your own time, you might like to try using JMeter to record and run load tests (see https://jmeter.apache.org/usermanual/get-started.html), but we will not include that in this lab.

C. Testability

Currently, the login() method is very limited – only one username and password are considered valid, and they are hard-coded into the app.

We would like to allow different forms of authentication to be selected at run-time. Besides being more useful in the end product, this has other advantages, such as modularity. Each sort of authentication can be encapsulated as a class and tested separately from the main web application.

To do this, we’ll alter the web app code to use a separate interface, “Authenticator”.

We’ve now made our web app more testable. Rather than being hard-coded, an Authenticator is created in the main() method, as the web app is started. Currently, our new Authenticator only ever returns false (indicating that a username and password combination is invalid). Alter the code so that one particular username and password are valid.

If we wanted, we could analyse the String[] args parameters passed to main() (or make use of the Java Properties API), and “slot in” different authenticators depending on how our web app was invoked from the command line. This is known as dependency injection – we have made our web app more independent from the authenticator.

As an exercise in your own time, you might like to try adding other implementations of Authenticator, which (for example) obtain lists of valid username+password pairs from a text file or a database.

D. Security testing

We have seen that some portions of the web app are restricted to logged-in users. From what has been covered in lectures and the text books, what are some sorts of security testing we could use to ensure that these restrictions are effective? Are there any other security practices we would need to bear in mind besides testing?