An organization needs to be Agile to succeed in the modern IT landscape. That’s "Agile,” with an uppercase “A,” because most companies now implement some degree of Agile methodology in their development process. It turns out they’re finding a lot of success.
However, not all Agile methods focus on the same software development life cycle (SDLC) stages. Because many methods partially overlap, it can be challenging to choose the most appropriate ones for our organizations. We would like to see practices that provide complete coverage of our SDLC and align with our corporate culture and philosophy.
Let’s consider two popular methodologies we’ll likely encounter: feature-driven development (FDD) and test-driven development (TDD). Although we can’t draw exact comparisons between the two, as FDD is a framework while TDD is a process, we’ll discover that each has noteworthy pros and cons. Also, these methods approach their areas of overlap from opposing sides. Unless we’re running a large organization, we’ll probably have to choose one or the other, so we’ll highlight some cases where we’re better off using a specific method.
Feature-Driven Development
FDD combines several industry best practices in a five-step framework. The first two steps address the project as a whole:
1. Develop an overall model.
This model determines our project’s scope and context.
2. Build a feature list.
FDD methodology focuses on the user’s needs, one of the possible parties represented by what it calls the “client.” FDD methodology defines features as small interactions the client wishes to perform—such as making an online reservation at a restaurant or viewing a client’s photo gallery—and breaks features into smaller pieces if they take longer than two weeks to implement. In this respect, features resemble Scrum’s user stories and sprints.
Each feature then iterates through the remaining three steps:
3. Plan by feature.
We assign ownership of features, as classes, to programmers.
4. Design by feature.
A chief programmer chooses the focus for two weeks, and team members review design packages before coding begins.
5. Build by feature.
Class owners develop their code, which someone tests and inspects before promoting it to the main branch.
Because they build features in isolation, developers often further reinforce this separation between development and deployment by using feature flags or feature toggles.
Test-Driven Development
We can sum up TDD in two expressions: write the test first, and only write code if necessary to pass a test. Although it’s technically feasible on large scales, TDD is much more effective when testing units remain small. The feature development cycle looks something like this:
- Add a test.
Developers begin working on a feature by writing a test that only passes if the feature meets specifications. - Run all tests.
This action should result in all tests failing, which ensures our test harness is working. It also shows that we need to write new code for this feature. - Write the simplest code that passes the test.
We’ll refine this code later in the process. - Rerun all tests, which should result in all tests passing.
If any test fails, we revise all new code until all tests pass again. - Refactor code if needed
And test after each refactor to preserve functionality.
Developers then repeat these last three steps for each new feature or piece of functionality. Approaching our development process this way encourages small, incremental tests, frequent commits, and lean code.
These two methodologies exhibit distinct pros and cons, many of which affect the same development stages. We’ll start by exploring some benefits of test-driven development.
TDD is a tooling process
Imagine that we want to introduce a new menu option on our ecommerce website’s homepage. Let’s say we want this new option to be a link to a discount page, and we want it to be visible only to logged-in clients. Integrating this feature in the code could look something like this:
if user.authenticated = true then
/* show the discount page link */
else
/* don’t show the discount page link */
This first stage is simple enough. But what if we decide we need additional requirements for validation beyond simply being signed in? Do we know how these specifications will affect our code, or are we hoping we won’t have to spend too much time hunting bugs after we make our changes? It would be nice if we could benefit from TDD’s “test it, validate it, approve it” approach, so we can ensure that all our features are reliable.
TDD forces us to quantify the exact specifications we must meet before we write and implement any code. Although it feels less natural than other approaches, TDD produces efficient, precise solutions.
In our menu option example, we know that this code is the most efficient solution to our client’s needs because it’s a product of the test suite we use to verify it. We know that we’ve ruthlessly eliminated code we don’t need. Our teams deploy streamlined, purposeful code, which they write to always pass tests.
Suppose we tolerate front-loading our testing this way and introduce some delay in our responsiveness to user requests. In that case, we can create applications that are typically much less likely to misbehave. The process encourages thorough documentation and enables teams to develop with the confidence that all product pieces function correctly at every stage in development. The specs are in: our foundry is tooled.
We may, however, encounter problems when we start to bring together all our product’s pieces. Although we can be confident that each feature works in isolation, we cannot be sure that it will reliably interact with others. TDD isn’t prepared for the planning we need in our largest apps and organizations, which might be why FDD is more prevalent in Big Tech. As TDD users, we may be stuck resolving compatibility issues between features, and between the tests we keep retooling.
Retooling is a lot of work
We’ll need to maintain this test suite alongside our codebase, impairing our ability to quickly add new functions that our tests don’t already cover. In a typical SDLC, we can expect to put at least as much work into maintaining this suite—effectively, retooling—as we put into our application code itself.
The work is never truly done. There will always be new needs for functional updates or business requests for additional features and settings. We also often face external demands, such as evolving regulatory standards. Developers relying on the TDD methodology must go through a complete testing cycle before each deployment and whenever the client’s requirements change.
Switching to TDD also requires a more significant commitment than what is typical of most process changes because we’ll usually need to apply the transition to our entire SDLC. Larger organizations sometimes use a hybrid approach, applying TDD to more self-contained products or features under a more traditional methodology across the whole organization. But, we generally lose many of TDD’s benefits if we apply this method inconsistently.
For this reason, TDD is often an ideal approach in smaller, more straightforward development projects. As such, it’s much more prevalent in shorter development loops. However, we should avoid TDD in more complex development streams, where FDD’s focus on planning and design is more appropriate.
FDD is versatile and comprehensive
Developers describe the FDD process as natural. Organizations approach their goals and deliverables from the top down. They plan and design first, build the code next, then have the option to launch new features in production using feature flags or feature toggles. Roles within the organization are clearly defined. FDD also replaces the frequent meetings typical of Agile frameworks with documentation.
Feature flags aren’t formally a part of FDD, but developers commonly use them to complement the methodology in almost any type’s functionality, including isolating security and performance incidents. The combination provides plenty of flexibility in how we deploy and lets us easily undo and isolate mistakes. We have total control of how a feature interacts with everything else over its entire lifecycle.
First, we can develop features without exposing them to the production environment on a two-week feature branch as part of FDD. When it’s time to merge back into production, we can wait for changes to propagate. We remotely provide access by flipping a switch when we’re sure we’ve caught any issues there. And in the event of a rude surprise, we can flip this killswitch to turn off the functionality instantly and keep our debugging invisible.
We implement feature flags in two parts: the code (a simple conditional wrapped around our feature) and a feature management platform. A large platform like LaunchDarkly enables us to develop complex deployments with granular control over user segmentation. It integrates with our workflows, provides analytics, and offers DevOps governance solutions that allow us to freely run tests anywhere in our stack, including in production.
Revisiting our earlier example, feature flags help us quickly gain granular control over access to the discount page link. We just need to install our preferred SDK with its API Key, which allows the app to check flag status in LaunchDarkly and ensure the code’s flag matches the one we set in our application. We might replace user.authenticated = true with enableFeature(on.click.nav), but from a coding perspective, that’s most of what it takes to deploy this feature. When we set the flag as active in our backend, that action validates and applies the user setting.
Conclusion
We’ve seen that TDD and FDD take opposing approaches to software development.
Test-driven development (TDD) is a well-defined process that creates lean, reusable, and automatable code. Although TDD is effective on small scales and when iterating conservatively, it’s less suitable for large or complex projects because it depends on a comprehensive test suite and universal implementation.
Feature-driven development (FDD) is a versatile framework that approaches development goals from the top down. It scales well, produces clear expectations, and breaks features into pieces of functionality that developers can achieve in two-week development cycles.
We also explored how we can use FDD and feature flags together to minimize the risk of deploying features, facilitate testing in production environments, and give developers more freedom in their code implementation. LaunchDarkly’s feature flags help support our FDD efforts in this way.
Request a demo to start implementing LaunchDarkly’s feature flags in your development process.