The Hidden Science Behind Facial Recognition

Some facial recognition technologies distinguish between pictures of a face and an actual face and some don’t. It depends on the technology being used. Apple’s facial recognition technology (called…

Smartphone

独家优惠奖金 100% 高达 1 BTC + 180 免费旋转




How we started unit testing Boozt iOS app

Written by: Boozt iOS Chapter

The codebase of the Boozt iOS app is around 7 years old. Like many codebases of similar age, the code was written largely without unit tests and without testability in mind. Over time, the drawbacks of not having enough test coverage became obvious, so we embarked on a journey to get started with unit testing.

The team took a pragmatic approach to introduce unit tests to the codebase. Knowing the inherent difficulties of adding tests to an existing codebase that wasn’t designed with testability in mind, the team decided against pushing out rigid guidelines on how and what to test. Instead, the focus lay on getting things going with small wins and gradually building up momentum.

One thing that the team did around the same time was to switch to the Clean Swift architecture for new feature development. Since a codebase’s testability is to a large extent decided by its architecture and patterns, we benefited significantly from the Clean Swift’s built-in testability.

Compared to the more traditional MVC approach, where it is easy for less-experienced developers to throw major functionalities into the controllers, Clean Swift has a clearer distribution of responsibilities into interactors, presenters and routers, etc. This made it a lot easier for us to test each individual component as we needed to. As the dependencies between different components are protocol-based, this made it easy for us to mock dependencies when we needed to, e.g. by using mocked view controllers instead of instantiating real views and controllers in the test cases to test presenter logic. This significantly shortened our test duration and improved the stability.

Testing new code that is written in Clean Swift gave the team wind in their sails. More tests were being added to the codebase, and more and more developers started to gain experience in writing tests (some learned to write their first tests!)

Here was where the real challenges kicked in — writing tests for the existing legacy code. It’s easy to instantiate an object with no or only a few explicit dependencies in tests, but doing the same to an object with a large number of implicit dependencies (e.g. via direct static method calls) takes skills and experience. By the same token, testing a function with clearly defined input and output is a breeze, but testing functions that have unexpected side effects can be frustrating, and could often lead to developers throwing in the towel.

There is a number of very good books that cover the latter, including the classic Working Effectively with Legacy code. Knowing these skills takes time and practice. To ease the learning process, instead of asking the developers to try them out and become frustrated, the principle of small wins was used. So instead of spreading the effort throughout the code to test for testing’s sake, the team focused their testing effort on the areas where they actively worked on. For example, when we started to fix a bug, we’d ask ourselves the question “why did this bug happen? How can we write a test to alert us against future regressions?”

We also started with relatively easier targets to add tests to. A utility that checks for a given URL pattern is a more practical target to test, than a giant class that handles push notification registration, inspects push notification payloads, and responds to different actions (more on this later). The latter is a really valuable target to test, but by sticking to our small wins principle, we made sure our developers had an easier path to ramp up to without getting drowned in the detail of dependency injection, inversion of control, etc from the get-go.

For more complex targets, like the push notification example above, we took the chance to inspect the code and ask ourselves “why is the code difficult to test”? More often than not, the difficulties were due to the code under test, taking on too many responsibilities, such as payload, parsing, action routing etc. Recognizing this, helped us refactor out certain responsibilities into their own functions or objects, which made testing significantly easier.

For some of the legacy code that was difficult to split into smaller pieces, we kept things flexible by putting them under integration tests instead of unit tests whenever that made sense. For example, we had a piece of code that fetched and cached server data if that version of data hadn’t been previously cached. These logics were rather hard to refactor out due to practical reasons, but testing the whole flow end-to-end wasn’t too hard. Hence, we added some integration tests to make sure the outputs were correct based on the setups.

These tests covered several layers of code, so when the tests failed, we did need to poke around to understand where things went wrong. But compared to not having any tests at all, we took an important step forward by wrapping this sub-system under tests in the first place, which gave us more confidence as we refactor that code and add more granular tests in the future.

With the above-mentioned approaches, over time we did notice an increase in the number of quality of tests written, as well as the number of developers who regularly wrote tests as part of their daily work. Our conversation started to shift from “is it worth writing a test for this” to “how do we test this”.

While we as a team are on a journey to level up our testing skills, we did keep an eye on the tests that were particularly difficult to write. When we found ourselves in a situation where we had to scratch our heads to figure out how to test certain things, we knew that this might be a sign that the code under test may need some rethinking. More often than not, a refactoring of the code under test could lead to both a simpler design and tests that are no-brainers to write.

If you enjoyed this article, and want to read more great stories from the Boozt platform team, be sure to subscribe to the Boozt Tech publication!

Add a comment

Related posts:

Cough Medicines for a Sick Patients

Cough is a protective reflex, its purpose being expulsion of respiratory secretions of foreign particles from air passages. It occurs due to stimulation of mechano-or chemoreceptors in throat…