Skip to main content

Loading…

Skip to main content
HomeProjectsPostsApproachesStackResourcesContact
Justin Tsugranes LogoJustin Tsugranes Logo

Justin Tsugranes

HomeProjectsPostsApproachesStackResourcesContact

Stay in the loop

Occasional notes on what I'm building, lessons earned, and the studio behind it.

By subscribing, you agree to receive No spam. Unsubscribe in one click anytime. from Justin Tsugranes. No spam. Unsubscribe anytime. Privacy Policy

© 2026 Total Ventures LLC. All rights reserved.

Privacy PolicyTerms of ServiceCookie Policy
← All approaches

Approach · shipping · in production

Tests as Design

I use tests as the initial design specification for new features, ensuring the API is usable and well-defined before writing implementation code.

Tests as Design is the practice of writing a failing test suite before any implementation code, using the test itself to define the desired interface and behavior. This approach forces a clear contract between the consumer and the producer of a function or component.

What it is

This is not merely Test-Driven Development (TDD); the emphasis shifts from testing to designing through testing. When I start a new feature, I don't open the implementation file. I open the `test.ts` file. There, I write the `describe` and `it` blocks that articulate how I expect to interact with the new functionality. This includes defining the function signatures, required input parameters, and expected outputs or side effects. The test becomes the first draft of the API, demanding a specific shape from the code that will eventually make it pass. It's a concrete specification, not an abstract idea.

Why I do it this way

I learned the hard way that unclear interfaces lead to brittle systems and expensive refactoring. By writing the test first, I am forced to think about the consumer's perspective immediately. This prevents over-engineering and ensures the API is intuitive and robust, rather than just functional. It also significantly streamlines my Agent-First Development workflow. When I hand a failing test suite to Claude Code, the agent has an unambiguous target. It's not guessing what I want; it's making the test pass within the defined interface. This clarity reduces iteration cycles and improves the quality of the generated code. For instance, when adding a new payment flow for Credit Packs over Subscriptions on Inky, defining the expected `handleSuccessfulPayment` function and its assertions in a test first ensures the Stripe webhook integration is clean and predictable.

How it works in practice

When a new feature is identified—say, a new backend service for Total Formula 1 or a content generation step for Pregnancy Power Hour—the first step is to create a new test file or add to an existing one. I write the `it` blocks, describing specific scenarios and their expected outcomes. For a Firebase callable function, I'd define its expected input and output types directly in the test call. For a UI component, I'd define the props it expects and assert its rendered output. This process naturally leads to type errors and runtime errors initially, which is the point. These errors highlight the missing implementation and guide the creation of the actual code. Only once the test clearly expresses the desired behavior and interface do I begin writing the minimal implementation code required to make that test pass. This applies across my stack, from Vercel edge functions to Resend email triggers and Stripe payment handlers that update user balances managed by Mercury.

Where this breaks down

This approach is less effective for highly experimental, throwaway code where the primary goal is rapid exploration without a long-term commitment to design. It can also feel slower initially, as the upfront design work is more explicit. For trivial getters and setters, the overhead of writing a dedicated test might not always justify the design benefit. Additionally, if requirements are extremely vague or constantly shifting, the test itself becomes a moving target, undermining the stability that this practice aims to provide. However, for most durable features that contribute to Multiple Revenue Streams, One Back Office, the investment pays off quickly in reduced bugs and clearer architecture.

FAQs

Isn't this just Test-Driven Development (TDD)?
Yes, it builds on TDD principles. The distinction is the explicit emphasis on using the test as the primary design tool for the API or interface, rather than just a verification step after design.
Does this slow down initial development?
It shifts time upfront to design and interface definition. While initial coding might seem slower, it significantly reduces rework, debugging, and refactoring later, leading to faster overall delivery of stable features.

I run this and four other brands. Want to see the operator playbook in detail?

Get the Builder's Playbook →
Written by Justin Tsugranes, Founder, Total Ventures· Founder, Total Ventures · U.S. Army veteran (13 years) · M.M. Jazz Studies, University of South Carolina
Last reviewed July 22, 2026

Related

  • CI Local Before CI RemoteI run all CI checks and validations locally before pushing code, ensuring immediate feedback and preventing remote build failures.
  • KISS, YAGNI, Rule of ThreeI apply KISS, YAGNI, and the Rule of Three to ensure I ship the simplest viable solution today, avoiding premature complexity and abstracting only when a pattern explicitly repeats.
  • Agent-First DevelopmentAgent-First Development is my method for optimizing codebases for AI agents to write and maintain most of the code, enabling me to scale product development as a solo operator.
  • Document While ShippingThis approach integrates documentation updates directly into the code commit process, ensuring that documentation always reflects the current state of the system.
  • Docs as SourceDocs as Source treats documentation, especially agent context files, as the primary source of truth for system behavior and product features, ensuring AI agents operate with current, explicit instructions.
  • Stop Short of PerfectionI focus development effort on the core user journey, shipping functional products with known imperfections outside that path, because over-polishing non-critical features delays market feedback.