One of the clear benefits of this new architecture is that key portions of debugging and testing, both during development and for regression identification, become much easier to handle than with traditional client/server applications. In this chapter, we will examine some techniques that are now available.
5.1 Debugging
In a traditional web application architecture, the frontend and backend must be initially debugged in isolation. The frontend and backend are likely written in different languages, may be developed by different teams, and may not even be able to run on the same machine. The interface between the frontend and backend applications is developed solely to a service API specification, and there is little ability for the frontend and backend to interact until both are nearly completed. Even once integration testing of the frontend and backend code begins, there is no easy way to use a single debugger to step through the code. In some environments, it may be possible to have a frontend debugger for watching the code running on the client, and a separate backend debugger, for watching the code running on the server. Other server environments do not provide any easy means of debugging, and developers resort to print or log statements in the code.
With an application developed with LIBERATED, many of those problems
are reduced or eliminated. Debugging of frontend and backend code need not be accomplished in isolation, both are written in the same language, and the service API can be exercised easily during development, allowing early and iterative debugging during the development process.
Of even more importance to the debugging process, the developer can now use a debugger running in the browser to step directly from frontend code into backend code, or set breakpoints in backend code and then interact with the user interface to cause a request to be sent to the backend... and immediately have the debugger stop at that breakpoint.
5.2 Automated tests
During development, it is common practice to write tests for small pieces of code or to test specific features or functionality. These tests are often called unit tests. In traditional client/server applications, unit tests would be written for the server side, to test the backend functionality. Separately, tests would be written for the client side, to test the GUI functionality. These are all valid tests, but neglect a key piece of functionality: the round-trip from client to server to client, via the application’s selected means of communication between the frontend and backend, e.g., RPC, REST, etc.
When the entire code can be tested purely within the browser, as is the case with an application based on LIBERATED, it is feasible to add the round-trip to the set of tests. Not only can backend remote procedure call implementations, for example, be tested in isolation, they can be called by tests via the same remote procedure call invocation that the application would use, to ensure that they work correctly that way too.
Web application testing can be automated with the use of third-party packages such as Selenium,1 which includes facilities for automating user interaction such as key presses and mouse clicks. Using such a package, in conjunction with a simulated
1http://seleniumhq.org
server running in the browser, complete user actions can be tested automatically.
Applications written using qooxdoo also have a built-in unit test framework that is designed for complete regression testing. A unit test application allows selecting tests to be run, or selecting a complete suite. Once tests are written to test a certain piece of functionality, any change to the code that breaks that functionality will be immediately obvious when the unit tests are run. The test suite can be set up to run automatically, e.g., every evening, to quickly catch code regressions.
The App Inventor Community Gallery project has, to date, over fifty qooxdoo-based unit tests that demonstrate this capability. Remote procedure call implementations are tested directly, for unit-level accuracy. Additionally, there are tests in which the full remote procedure call path is confirmed to be working correctly. These tests issue a remote procedure call request in the same manner as the real application does, which creates the JSON-RPC request and sends it via the selected transport, initiating parsing of the request, which causes the appropriate RPC implementation to be called, which then similarly returns the result. There are not yet any tests that exercise GUI-initiated requests.
5.3 Debugging Experience
During the course of developing the App Inventor Community Gallery, the LIB-ERATED architecture time and again proved itself to be a highly efficient and easy to use development and debugging environment. Instead of developing the frontend and backend code in isolation as might be done in a traditional client/server environment, new user interface features and any corresponding backend changes are developed and tested concurrently. When new code does not work as intended, a typical debug cycle is:
1. Set a breakpoint in the remote procedure call implementation in the backend code. Run the program.
2. If the breakpoint in the RPC is hit, review the received parameters to ensure they are as expected. Step through the RPC implementation, noting variable
changes, return values from functions, etc., until the problem is identified.
3. If the breakpoint in the RPC implementation is not hit, this indicates that there is likely a problem in the way the RPC is called. Set a breakpoint in the new frontend code, where the remote procedure call is initiated.
4. Run the program again, and at the breakpoint, ensure that the proper remote procedure call is being requested, and that the parameters have the expected values. If not, fix the problem, and repeat the process.
5. If, upon running the program in the previous step, the breakpoint is not hit, normal frontend debugging procedures are used to ascertain where the code is faulty.
6. In the early days of LIBERATED, while bugs in the application communication protocol (JSON-RPC generation and parsing) were still being worked out, it was also occasionally useful to step from the frontend code that issued the remote procedure call, through the code that created the JSON-RPC request and queued the message for the simulated server, etc., all the way into the RPC implementation in the backend code. This would be a useful technique again, for an application which desired a different application communication protocol.