Skip to main content
Test Double company logo
Services
Services Overview
Holistic software investment consulting
Software Delivery
Accelerate quality software development
Product Strategy & Performance
Level up product strategy & performance
Legacy Modernization
Renovate legacy software systems
Pragmatic AI
Solve business problems without hype
Upgrade Rails
Update Rails versions seamlessly
DevOps
Scale infrastructure smoothly
Technical Recruitment
Build tech & product teams
Technical & Product 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 Strategy & Performance
Level up product strategy & performance
Legacy Modernization
Renovate legacy software systems
Pragmatic AI
Solve business problems without hype
Cycle icon
DevOps
Scale infrastructure smoothly
Upgrade Rails
Update Rails versions seamlessly
Technical Recruitment
Build tech & product teams
Technical & Product 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
AI in the workforce: A shifting talent equation

AI is transformative: reshaping the workforce, blurring roles, and breaking career ladders. Explore how orgs can balance people, technology, and business in this new era of work.

by
Jonathon Baugh
Leadership
Leadership
Leadership
The anvil of alignment: The value of monoliths over microservices

Before dismantling your monolith for microservices, understand its crucial role in organizational alignment. We explore hidden costs, Conway's Law, and strategic insights for leaders making architectural decisions.

by
Dave Mosher
by
Joel Helbling
by
Steve Jackson
by
Patrick Brown
by
Jonathon Baugh
Developers
Developers
Developers
Build common (virtual) ground with unstructured remote calls

Taking remote work to new levels takes new techniques. Explore how unstructured remote calls can help build trust.

by
Tyler Sloane
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 StrategyLegacy ModernizationPragmatic AIDevOpsUpgrade RailsTechnical RecruitmentAssessments
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.