How do you test changes in a large project? This question was asked during a recent live weekend class by Rushton W. In the class, I was explaining the benefits of compiling and testing changes often and Rushton wanted to know how this worked in a large application like might be found in a major software company.
This episode explains three activities you can take to help you test your changes.
The first is to make use of automated tests including unit tests. One of the first unit tests that I always write when working on a new class is just a test that makes sure that the class can be constructed.
The second is to make sure to involve the customer. This can really be whoever is going to use your program. Use feedback from the customer to help you write more tests.
The third activity is to adopt a design that will allow you to test your changes without needing to build everything and perform a full application install. You want to have small components that are easier to test with unit tests but also easy to just build that one component and swap it into your application for more integrated testing.
Listen to the full episode or read the full transcript below.
There are many ways to go about this and you shouldn’t take this explanation to be the only way. What I explain here has worked well for me. Let’s say that you’re just getting started on a new project. There’s a concept called the minimum viable product, or MVP, that you’ll want to focus on first. That’s a really good topic for another episode. I’m going to keep this episode focused on answering the question about the best way to test your changes.
You’re not going to be working on the MVP directly because something like that is usually too big. It can also be intimidating to think about an MVP as a whole. There are software patterns that can help you identify solid designs that have been proven through countless projects. I’ll go over various patterns in other episodes.
Let’s say for now that you need to be able to read a text file and follow some instructions contained in that file. You’ll want a class that knows how to read the file and return other objects that represent the actions you need to take. This is a good tangible place to start and will give you a result that can be demonstrated when you’re done.
Now that you have a place to start, follow these activities to make sure that you can test your changes. The times when I’ve been on projects that struggled, were usually because we weren’t doing one or more of these three main activities.
The first activity is making sure you have automated tests. This includes unit tests. Ideally, you’ll have a dedicated quality assurance or QA team that can write code just like the development team. It really helps if you write a new unit test first before writing any code in your project. In this example, the first unit test that I’d write would just make sure that my new class to read a file can be constructed. Let’s call this class fileParser because it works with files and parsing just means reading and understanding the contents of something. You write the test before you even create the fileParser class.
Why would you do this? Why write code that won’t even compile? Well, we’ll fix the compilation problem in just a moment. But the reason to write the test first is so that you can take on the role of a consumer of your class. You can write code that constructs an instance of your fileParser in a natural way. I’ve seen it happen too many times where I’ve jumped in and written a class only to later find it clunky and hard to use. It seemed perfectly reasonable when I was writing the class. Once you write the construction test, then create the class and add just that one constructor. Then build and run your tests. Keep doing this. Write a test, write the code to get it to compile, and write just enough code to pass the test. Then add more tests.
You’ll eventually get to the point where you make a change to get your new unit test case to pass when a completely different test fails. That’s great! You just caught a bug that would’ve probably gone unnoticed without extensive unit tests because all your attention is on the new changes.
The second activity is keeping your users involved. If you follow the principle of always working on an MVP, then at any given time, you should have something that the customer will find useful. Within reason anyway. There was one very successful project that I worked on where we delivered a version of the product that had little more than a tree control to browse objects in a hierarchy. At least three quarters of the project was completely missing and the customers loved it because even that little bit of browsing gave them a viewpoint that was missing in other products. This project still exists today almost 15 years later and has been widely adopted and copied. The biggest reason I can think of for the success is that we kept the customer in mind and always worked on adding value. And writing tests is a great way to make sure that you’re meeting your customer’s expectations. Use feedback from your customers to add more tests.
The third activity is architecting your program so that it’s composed of smaller modules. Don’t try to squeeze everything into a single executable. This doesn’t mean that you have to have multiple executables although sometimes, that’s exactly what you need. This is really making sure that you have independent libraries and services that are responsible for specific tasks. This will really help as your product gets more complex. A full build of everything might take anywhere from several minutes to a half hour or longer. And that’s just the beginning. Imagine if in order to test your new change you first had to uninstall the previous version and go through a full product installation each time. You’d be lucky to get through a handful of changes each day. If I had to guess, I’d say this is the main reason why developers abandon the activity of making small focused changes and testing often. If you have forty small changes which you can easily get to by creating several unit tests for each class method, then you just can’t allow this to take a whole week where the majority of the time is spent building, uninstalling, and reinstalling.
You need to be able to instead just build the component that’s affected by your change. This could be a module, library, service, etc. This should take no more than a minute. Then run your unit tests which help make sure that your changes work within the limited scope of the tests. Then you should be able to swap your new component into your current installation for more integrated tests.
If you want to combine several runs of your unit tests before swapping your changes into a running installation, then that’s okay. It might even be necessary because it could take you anywhere from minutes to several hours to get enough code written to be able to see any different behavior at the application level. Just don’t let this go too long. Remember that your unit tests don’t have the vantage point of the entire application.