One of the most intuitive ways to work with software is to make a change and inspect the modifications in its user interface. While it seems easy and intuitive, I think it deserves a post on its own because it so essential and can be easily misused.
Its only requirement is that you have to be able to build and start the application on your machine. Usually, this is easy, but there are exceptions. If the system is too complex or has many dependencies, it might be challenging and time-consuming to start the whole thing locally. In this case, it might be handy to have access to a staging environment where you can deploy your changes easily. Docker might also be useful in this situation as it allows to package the application with its dependencies, so you don’t have to manually install everything on your computer.
Because it’s usually cheap and requires very little effort to set up, it’s a great way to get involved in a new project. It’s also a good starting point if you work with an application that has poor infrastructure or tooling to support another kind of verification strategies.
It’s feasible to use manual testing to verify things that can’t be reliably or cost-effectively tested otherwise. For example, when developing the layout of the UI of your application, chances are that the best thing you can do is to manually refresh the browser from time to time and check the results.
While it’s not directly an argument for manual verification, it’s worth checking out David Heinemeier Hansson’s post about Test-induced design damage, and the discussion it sparked with Kent Beck and Martin Fowler.
Based on this discussion I think it’s safe to say that relying on manual verification in certain parts of the application can lead to a cleaner design, as the original vision doesn’t have to be distorted just for the sake of testing or other verification strategies.
Usually, this technique only supports long feedback loops. Because it requires the whole application to be up and running, there is significant time between implementing a small change and seeing it in action.
The delay usually consists of two things:
- redeploying and restarting the application
- reproducing the state necessary to test the changes (for example, signing in and wading through a bunch of menus)
Depending on your stack, you might get away with some of these annoyances with hot code deployment. If you rely heavily on this strategy make sure to constantly seek opportunities for shortening this time.
However, using this technique excessively can be time-consuming for other reasons as well.
Because you have to manually test everything after each change, this technique scales poorly with the number of cases you have to check. As a consequence, it’s hard to implement changes in complex modules solely by utilizing this strategy. Not to mention that it can quickly become really boring to recheck the same thing multiple times.
Also, it only makes possible to observe the externally visible behavior. It doesn’t matter if you know the exact place of a bug in the source code: you have to fight your way down to the code in question from the user interface. You have to carefully craft input data, to get the desired conditions for a function where you suspect a defect. Needless to say that you also have to deduce the internal state from the visible output.
Note to maintainers
In the end, most tasks involve some amount of manual testing. It doesn’t matter how you created the changes and what automation you have to verify correctness, you should check that the feature you’ve implemented integrates nicely with the overall user experience of the product and works well with other parts of the software.
Because it’s so essential, anybody who is responsible for maintaining a software (or acts as a lead in a team) should aim for making it more accessible for newcomers.
- Make the application easy to build and run. Avoid having too many external dependencies. If you happen to have them, try Docker. Write a clear guide on how to install the required dependencies and build the application.
- Provide mocks for external services where possible to reduce external dependencies required for development.
- Provide sensible initial data, so one can experiment with the application right from the start.
- Make it easy to develop with various IDEs and workflows. Everyone has a preference. Don’t lose developers because you only support that other tool.
- Seek to ease typical development tasks. Say you develop a UI heavy web app for a client who is really picky on user interface design details. Is it possible to edit the UI related code without restarting the application every time?
Making your application more accessible to developers is beneficial for everybody, as it will make deployment and maintenance easier as well.
It’s essential for every developer in a team to be able to start the application and see it’s working as a whole in some way. It can affect delivered quality, sense of ownership, and without it, you can’t make a full, business-impacting change on your own.
Because it can be time-consuming, it’s important to find the balance, and recognize the limits of this technique. As a rule of thumb, don’t try to check every little change through its interface, but make sure to test the most critical user scenarios at least at the end.