Skip to main content
Test Double company logo
Services
Services Overview
Holistic software investment consulting
Software Delivery
Accelerate quality software development
Product Management
Launch modern product orgs
Legacy Modernization
Renovate legacy software systems
DevOps
Scale infrastructure smoothly
Upgrade Rails
Update Rails versions seamlessly
Technical Recruitment
Build tech & product teams
Technical Assessments
Uncover root causes & improvements
Case Studies
Solutions
Accelerate Quality Software
Software Delivery, DevOps, & Product Delivery
Maximize Software Investments
Product Performance, Product Scaling, & Technical Assessments
Future-Proof Innovative Software
Legacy Modernization, Product Transformation, Upgrade Rails, Technical Recruitment
About
About
What's a test double?
Approach
Meeting you where you are
Founder's Story
The origin of our mission
Culture
Culture & Careers
Double Agents decoded
Great Causes
Great code for great causes
EDI
Equity, diversity & inclusion
Insights
All Insights
Hot takes and tips for all things software
Leadership
Bold opinions and insights for tech leaders
Developer
Essential coding tutorials and tools
Product Manager
Practical advice for real-world challenges
Say Hello
Test Double logo
Menu
Services
BackGrid of dots icon
Services Overview
Holistic software investment consulting
Software Delivery
Accelerate quality software development
Product Management
Launch modern product orgs
Legacy Modernization
Renovate legacy software systems
Cycle icon
DevOps
Scale infrastructure smoothly
Upgrade Rails
Update Rails versions seamlessly
Technical Recruitment
Build tech & product teams
Technical Assessments
Uncover root causes & improvements
Case Studies
Solutions
Solutions
Accelerate Quality Software
Software Delivery, DevOps, & Product Delivery
Maximize Software Investments
Product Performance, Product Scaling, & Technical Assessments
Future-Proof Innovative Software
Legacy Modernization, Product Transformation, Upgrade Rails, Technical Recruitment
About
About
About
What's a test double?
Approach
Meeting you where you are
Founder's Story
The origin of our mission
Culture
Culture
Culture & Careers
Double Agents decoded
Great Causes
Great code for great causes
EDI
Equity, diversity & inclusion
Insights
Insights
All Insights
Hot takes and tips for all things software
Leadership
Bold opinions and insights for tech leaders
Developer
Essential coding tutorials and tools
Product Manager
Practical advice for real-world challenges
Say hello
Developers
Developers
Developers
Testing

Adversarial testing: A slightly unorthodox testing philosophy

Discover how an adversarial testing approach can make unit tests exciting and effective by treating code like an opponent to outmaneuver. Say goodbye to frustrating tests and hello to fun!
Pam-Marie Guzzo
|
June 7, 2022
Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.

Pretty early in my career as a software developer, I realized the traditional approach to unit testing wasn't working for me. The back and forth of writing just enough to fail then fixing, then writing just enough to fail then fixing, is a bit too disconnecting for my brain to really enjoy. Try as much as I might, the extreme breaking down of tasks this way left me frustrated, unmotivated, and generally sad. I couldn't do it.

Luckily for me, I'm an incredibly stubborn person and had no intention of quitting. Instead of giving up on testing, I experimented with the approach until I found something that made me really love writing unit tests. I love it so much that I actually jump on the chance to refactor existing test suites and fill in testing gaps! Now I don't get mad at tests — I get even. (Insert awesome guitar riff here.)

A person in a leather coat leaning back playing an electric guitar.
I am totally this cool in real life. Photo by jehafo

Step 1: Fine, I guess I'll write some happy tests first

Alright, let's go with a simple example of some function that takes in a word and returns the number of letters (in JavaScript because I'm lazy) and a little happy test for it using Jest.

let howManyLetters = word => word.length

it('returns the right number of letters', () => {
  expect(howManyLetters('it')).toBe(2) // Pass
})

Awesome! 100% test coverage right there friends! Time to pack up!

Step 2: Blow it up

Honestly, for something this simple that's probably where a lot of people would stop, right? Not me, though! I don't trust my code or anyone else's, so I put on my attack face and start poking.

let howManyLetters = word => word.length

it('returns the right number of letters', () => {
  expect(howManyLetters('it')).toBe(2) // Pass
})

it('explodes as expected', () => {
  expect(howManyLetters(undefined)).toBe(0) // Fail
})

it('works, but that is not a word', () => {
  expect(howManyLetters(['it', 'works'])).toBe(2) // Pass
})

it('works, but was I allowing multiple words?', () => {
  expect(howManyLetters('it works')).toBe(8) // Pass
})

‍
This example is hilariously oversimplified, but I hope it's demonstrative of the goal. My approach isn't to test whether or not the function works, but all the ways it could go wrong. The larger and more complex the function, the more likely it is something weird could happen. And I want to know what that weird thing is and account for it long before the code is in production.

What I said earlier about not trusting anyone's code is important too — I'll make somewhat heavy use of test doubles in my unit tests in order to force something weird to happen. Even if it's really unlikely for the other function I'm calling to fail, I'll use a double to throw something weird just to see how my code handles it. Does it throw an unhelpful exception? Can I see what failed and where? Do I get a nice log telling me what went wrong?

Usually what I see going into existing test suites is a focus on exercising the logic and confirming all possible happy paths are covered. I've seen code bases with 90% coverage that are wildly painful to debug when something does go wrong because of this. Coverage can be a good indicator of what's missing, but if you aren't facing your code down like a toddler you're trying to dress when running late, you're going to miss things. (Alternate metaphors for other life paths: Like a puppy you're trying to make drop the suspicious thing it found in the woods; Like a bear who figured out you have meat in your cooler; Like a quarterback one touchdown away from winning in overtime.)

I've found this approach to testing scratches a "what would happen if ..." itch for me that lets me go wild on unit tests in interesting ways. With this approach, I've also found and resolved a number of surprising bugs that otherwise would have been missed (until a user stumbled on it).

Now let's go up a level.

Step 3: About those integration tests

Time to grab the function or route that calls all the other functions and routes and have a party! My general rule for integration tests is to focus on the absolute most critical paths and then just blast the failure handling. (After all, your unit tests should cover all the weird internal logic, right?)

I find this to be extremely important with REST APIs, especially as they are most likely to be seen by external users. I generally try to follow what one of my mentors called the 5 o'clock Friday Rule: no matter what goes wrong, you want enough information that you can fix it quick enough to go home at 5 pm every Friday.

Keeping this in mind, I always care more about all the ways things can go wrong than any of the ways things can go right. I hate overtime. With a passion. It's the worst. My entire testing strategy is to avoid overtime.

Nice try evil code!

Actually, I think that's the core of this whole philosophy. The code hates me and wants me to work overtime, so I'll find all the ways it can ambush me and stop it in its tracks.

Taking an adversarial approach to testing is how I got past my general dislike of repetitive tasks. Approaching the code like an opponent in a game I need to outmanoeuvre lets me see all the ways it can go terribly wrong. Plus, breaking things is fun! Just ask that toddler!

Related Insights

🔗
Why mastering testing is hard: The necessary and sufficient rule
🔗
How to stop hating your tests
🔗
Happier TDD with testdouble.js

Explore our insights

See all insights
Leadership
Leadership
Leadership
The business of AI: Solve real problems for real people

After participating in the Perplexity AI Business Fellowship, one thing became clear: the AI hype cycle is missing the business fundamentals. Here are 3 evidence-based insights from practitioners actually building or investing in AI solutions that solve real problems.

by
Cathy Colliver
Leadership
Leadership
Leadership
Pragmatic approaches to agentic coding for engineering leaders

Discover essential practices for AI agentic coding to enhance your team’s AI development learning and adoption, while avoiding common pitfalls of vibe coding.

by
A.J. Hekman
by
Aaron Gough
by
Alex Martin
by
Dave Mosher
by
David Lewis
Developers
Developers
Developers
16 things software developers believe, per a Justin Searls survey

Ruby on Rails developer Justin Searls made a personality quiz, and more than 7,000 software developers filled it out. Here's what it revealed.

by
Justin Searls
Letter art spelling out NEAT

Join the conversation

Technology is a means to an end: answers to very human questions. That’s why we created a community for developers and product managers.

Explore the community
Test Double Executive Leadership Team

Learn about our team

Like what we have to say about building great software and great teams?

Get to know us
Test Double company logo
Improving the way the world builds software.
What we do
Services OverviewSoftware DeliveryProduct ManagementLegacy ModernizationDevOpsUpgrade RailsTechnical RecruitmentTechnical Assessments
Who WE ARE
About UsCulture & CareersGreat CausesEDIOur TeamContact UsNews & AwardsN.E.A.T.
Resources
Case StudiesAll InsightsLeadership InsightsDeveloper InsightsProduct InsightsPairing & Office Hours
NEWSLETTER
Sign up hear about our latest innovations.
Your email has been added!
Oops! Something went wrong while submitting the form.
Standard Ruby badge
614.349.4279hello@testdouble.com
Privacy Policy
© 2020 Test Double. All Rights Reserved.