top of page
  • Writer's pictureShaw Innes

I’m Scared to Change That

Adapted from the “I’m scared to change that” talk given in the SixPivot Community Mentoring Program. Please enjoy and feel free to use resources links at the bottom.

Modifying big, complex, or unfamiliar software can be a daunting task, fraught with peril. We’re going to go over some testing methods and tooling options to help you gain confidence in making changes to complex or unfamiliar software. We’ll be covering:

  1. Automated tests and test selection

  2. Tooling

  3. Understanding and improving code

This article is aimed at beginner-to-intermediate developers, but serves as a good refresher for anyone.

Unit Tests are Good

We all “know” that Unit tests are good (for our purposes, let’s say ‘Unit Test’ covers all code-based tests). So that we’re on the same page, here’s a quick recap of why:

  1. Repeatable – they can be run the same way over and over again

  2. ‘Cheaper’ than manual testing – running is quick and cheap. You ‘lose’ time writing them, but you’re also writing and executing fewer manual test plans

  3. Automatable (apparently that’s a word) * – they can be run automatically by tools

  4. Finds bugs early – the act of writing tests often reveals bugs in code that people think have no issues

  5. ‘Regression’ (ensuring changes do not break existing functionality) – since they are cheap and repeatable to run, they can be used to ensure changes don’t break existing functionality

  6. Trackable * – when run automatically by tools, test counts, pass rates, breakages, execution times can all be tracked and improved

  7. Makes integrating changes easier/safer * – again, having tools run tests at stages such as commit and Pull Request ensures changes integrate well with other changes

  8. A form of ‘Code Documentation’ – other developers can read and understand the intent of the code

(*) These benefits are gained when the running of the tests is automated in a build tool, such as DevOps, TeamCity or Jenkins

Types of Automated Tests

You may have seen Testing Pyramids before. Every source has their own take on what the levels are.

While there are a variety of ideas across different authors, they all share some common ideas

The ‘foundation’ of your testing triangle is made up of the cheapest, fine-grain, minimal-responsibility, fastest tests you create, whereas the top portion tests more components in a more complex, often slower, way.

The lower portions of the triangle are cheaper and quicker to create and run, and the higher portions are more ‘expensive’

Important Forms of Automated Testing

It’s important to be aware of the different forms of automated tests. There is some overlap between types of tests, and many sources have slightly different classifications, however the following list should cover the majority of cases.

Unit Tests Test ‘units’ – single functions and how they act upon inputs and dependencies. Dependencies are mocked

Approval Tests Approve and save the output of a test to compare against future test runs

Component Tests Several classes (or the whole app) work in concert with the ‘boundaries’ mocked

Acceptance Tests (in this context, Behaviour tests) A form of unit and/or component test that asserts scenarios

Integration Tests Tests that act on the system while it is connected to the downstream systems, such as Databases, APIs, Queues etc

UI Tests Automated tests that drive a Web Browser to interact with the application, e.g. Selenium or Cypress

Performance Tests Test Suites that run large workloads against large datasets to help minimise performance degradation

Smoke Tests A subset of UI, Integration, or manual tests that give quick confidence that the system as a whole works correctly

Manual Tests A person manually performs test types such as Integration, UI, or Smoke tests

An Aside – TDD or No TDD?

Test Driven Development is the concept of writing tests first, and then implementing your method/component to successfully pass the tests.

This method of testing helps many people design code that is testable and has good test coverage

However, you do not need to follow TDD practices as long as the code you produce is testable. How?

What if there are little-to-no tests?

Your confidence in change will be proportional to the effort invested in validating the change

But what if we need to make a change and there are no tests?

An Approach:

  1. If you aren’t experienced with this application, familiarise yourself with the application and the area you will be modifying

  2. For unfamiliar codebases, do not attempt to understand all parts in detail. Get an overall ‘feel’ and understanding of the application, then drill down. This should include spending time with experts or users where possible. Talking to people can help you come to a better-shared understanding of the domain and its intent.

  3. Understand the Scope of your change

  4. Investigate and note the implications of your change on the wider application

  5. Tooling will help you understand the implications of a change – parts of the system this change will affect. E.g. ‘Find Usages’ or even just ‘Search’

  6. Write some tests to assert the current functionality

  7. Write tests at a ‘level’ that has the least friction. E.g. if the code isn’t very ‘testable’, consider that Component Tests might be the most suitable type of test to start with

  8. IMPORTANT – If you do not create these tests first, if/when you find an error, you may not know if it was existing or introduced

  9. This step may require refactoring. If so, try to maintain the original algorithms as much as possible

  10. Perform your change

  11. Try to reduce Code Smells as you go. Follow the ‘Scout’s Rule’ of always leaving things better than when you found them

  12. Ensure the tests are updated to assert the correctness of the change (before or after making the change, TDD or not)

  13. Expand your tests further as much as you are able before moving on from the task

  14. Take your new-found test glory as far as you can within the bounds of your team charter/project/agreements

Tools

Here is a quick list of examples of tools that can assist in the discussed strategies:

Unit Test Libraries

Code libraries to write unit tests with

NUnit, XUnit, Jasmine, Karma etc. Also ‘Mocking’ libraries such as NSubstitute or Jest

UI Test Libraries

Selenium, Cypress etc

Build Tools (CI/CD)

Automatically run tests on Commit, on Pull-Request (code integration) and Merge.

Can also be used as a platform for running Integration, UI and Performance tests continually, or on a schedule

Azure DevOps, Jenkins, TeamCity

IDE / Code Editors

Robust, modern editors have a plethora of helpful tools for understanding the implications of your changes, such as

Code Navigation – symbol usage analysis, static analysis (capturing invalid or potentially problematic code).

Refactoring – Resharper, in particular, has strong assistance for refactoring and detecting ‘smells’ E.g. Visual Studio, VS Code, Rider, Resharper

Recap

To gain confidence in change:

  1. Have good coverage of a variety of test types that assert your system and its components are working as expected

  2. Ensure the tests are running automatically and consistently

  3. When a system is poorly tested, start testing the most frictionless way possible to gain momentum

  4. When changing untested components, start by writing tests that assert the current understanding of the functionality, then make your change

  5. Use tooling to help you understand systems and dependencies

  6. Understand the system you are changing from the point of view of understanding the implications of change

Gaining confidence in changes not only reduces the stress of shipping changes, but also reduces overall work by minimising bugs, and easing the weight of required processes around change.

Resources

Unit Testing

Code Navigation

Refactoring

bg1.webp

SIXPIVOT BLOG

OUR INSIGHTS

bottom of page