Optimizing Your Testing Strategy: Unleashing the Test-Beast

rozeri dilar
iOS App Mastery
Published in
4 min readJan 29, 2024

Parallelizing test execution & Randomizing Order

If you ever encounter performance issues with your tests (e.g., large test suite), try executing the tests in parallel as it could help to speed up the testing process.

  • Picture this: You lay your cards in perfect order, but then your pesky cat decides to leap onto the table. Cards fly everywhere. Randomizing? Accidental genius! 🃏
  • When tests are intertwined like a bowl of spaghetti, things get messy. Randomizing the test order is like that friend who borrows your stuff and never puts it back correctly. Annoying? Yes. But they’ll expose any order dependencies, making sure your tests can stand alone like the heroes they are.

Example: If your “LoginTest” always runs before “PurchaseTest” and everything’s fine, but “PurchaseTest” fails miserably when run first, you’ve got a sneaky dependency.

Code Coverage: Not Just a Fancy Number

  • If your code was a puzzle, 100% code coverage means all pieces are on the board. But does the image make sense? Not always! Test the behaviors, not just the lines.
  • Code coverage basically shows which lines of the production code have been executed during testing. Having 100% code coverage means that all the production code has been executed by the tests but that does not mean that all the behavior of the application has been tested. Checking code coverage should be important only when the developer wants to see which part of the code has not been tested yet. Having a 100% coverage should not be the goal, the goal should be testing all behaviors, when testing all the behaviors with TDD code coverage will be 100%.

Example: It’s like having all the ingredients to make a pizza, but did you actually make the pizza or just throw everything into a bowl and call it a day?

Breakpoint Boogaloo: Adding a test failure breakpoint in debug

When tests fail, dive right into the action! It’s like watching a movie and pausing right at the climax to analyze every detail. By adding a test failure breakpoint, we can directly see which part of the code will crash. It helps a lot during development.

Example: Imagine a “whodunit” movie. Instead of waiting till the end, you stop at the juicy part and figure out who the culprit is. Detective mode is ON!

Speak Fluent Testish: Write declarative failing cases

Getting the right feedback makes tests extremely valuable. You don’t have to lose time to find what’s wrong you want your tests to indicate exactly why they failed and where.

  • Your test fails. You squint. You tilt your head. Huh? Write tests that scream their problems, not whisper.

Example: Instead of a test saying, “Something’s wrong,” it shouts, “Hey! Your shopping cart can’t handle more than 99 rubber ducks!” Quack-tastic feedback, right?

The Grand Tour of End-to-End Tests:

  • Think of these as those epic road trips. They’re long, sometimes unpredictable, but essential for the full experience. 🚗💨
  • Run them occasionally, like checking if that old diner at the end of Route 66 still makes the best pancakes.
  • Add a new test bundle scheme for end-to-end API tests, and run them in separate test targets in isolation. End-to-end tests follow the same principles of automated testing; however, they differ significantly from unit tests in some key areas. Because end-to-end tests check the behavior of various components in integration (including remote servers, databases, the file system…), they may be unreliable and may require substantially more time to run than unit tests. In other words, end-to-end tests can be very costly to create, run, and maintain, when compared to unit tests. End-to-end tests can become a liability for a team that if solely relies upon them for checking the behavior of the system.
  • We cannot run the end-to-end tests all the time, because they dont give fast feedback as unit tests, you might run them less often. When we run them while developing, we will need to wait too long for the results, so it might be better to write everything then run the tests. An even better solution would be running them before merging to the master branch, through a continuous integration pipeline. You can automate and create a smoother process within the team.
  • Ideally, we want to minimize the end-to-end test count by relying more on fast, precise, and reliable unit tests. When dealing with remote servers, we can minimize the number of end-to-end tests as long as we are confident that the contract between the client and server is respected and it doesn’t change (or when the contract changes we, the client, are notified of the changes). The less we trust the counterpart, the more end-to-end tests we will need to add against it to boost our confidence and minimize the risk.

Example: Instead of just checking if the car engine starts (unit test), you’re driving cross-country to see if the tires last.

Factory Fun with a Twist:

Create a factory method for SUT and other objects in the test suite: By that when you need to add a new dependency injection, you won’t have to go and update all the test methods. Also, add memory leak checks to factory methods as well. While adding memory leak tracks, make sure to add all the dependencies of the SUT to be sure that all the objects while working together in integration, will not generate any memory leak.

Example: It’s like making a toy in Santa’s workshop, but also ensuring it doesn’t eat all the cookies. 🍪

Grab your developer cape, wizard hat, or detective magnifying glass, and make your testing legendary. Let the beasts of randomness, parallel universes, and detailed feedback guide you. Happy bug hunting! 🐞🔍🎉

https://github.com/rozeridilar/Example-App-Data-Races-iOS/blob/c6d957607bdc3913c5af03a5458acc5dce6e9d2f/Tests/Example-Data-Races-In-Image-LoadingTests/ImageCacheTests.swift#L9

--

--