There is a debate now and then about the value of typed JavaScript. "Write more tests!" some opponents shouted. "Replace unit tests with types!" others shouted. Both are right in some ways and wrong in others. Twitter's space is not enough to reflect the subtleties. But in this article, we can try to elaborate a reasonable argument on how and how the two should coexist.
Correctness: What we really want
It's best to start with the results. What we really want from all these meta-engineering is correctness . I'm not referring to a strict theoretical computer science definition, but a more general adherence to program behavior and its norms: there is an idea in our mind about how programs work, and the programming process organizes bits and bytes, making this idea a reality. Because we don't always know exactly what we want, and we want to be sure that our program doesn't break when making changes, we write types and tests on top of existing original code just to make things work in the first place.
So if we accept that correctness is what we want and that types and tests are just automated ways to achieve this, it is better to have an intuitive model to show how types and tests help us achieve correctness, thus understanding where they overlap and where they complement each other.
Visualization model of program correctness
If we imagine the entire infinite Turing-complete possible space of all the operations that a program may perform ( including errors ) as a huge gray area, then what we want the program to perform, our specification, is a very, very small subset of that possible space (the green rhombus in the figure below, exaggerated in size to display something):
Our job in programming is to align our programs with specifications as much as possible (of course, we know that we are imperfect, our specifications are constantly changing, such as due to human errors, new features, or unspecified behavior; therefore we never completely manage to achieve complete overlap):
Note again that for the purposes of our discussion here, the boundaries of procedural behavior also include both planned and unplanned errors . Our meaning of “correctness” includes planned errors, but not unplanned errors.
Testing and correctness
We write tests to make sure our program meets our expectations, but there are many options for what to test:
The ideal test is the orange dots in the figure – they accurately test whether our program overlaps the specification. In this visualization we don't really distinguish between test types, but you can think of unit tests as very small points, and integration/end-to-end testing is the big points. Either way, they are points, because no test can fully describe every path in the program. (In fact, you can have 100% code coverage, but still can't test every path because there is a combination explosion!)
The blue dot in the picture is a bad test. Of course, it tests whether our program works, but it doesn't actually pin it to the underlying specification (at the end of the day, what we really want to get from the program). This test breaks when we fix the program to align more closely with the specification, giving us a false positive.
The purple dot is a valuable test because it tests how we think the program should work and determines areas where the program is not currently working. Leading with purple testing and fixing the program implementation accordingly is also known as test-driven development .
The red test in the picture is a rare test. It is not a normal (orange) test that tests the "happy path" (including the wrong state of the plan), but a test that expects and validates the " unhappy path" failed. If this test "passes" where it should "fail," it's a huge early warning sign that something is wrong -- but it's basically impossible to write enough tests to cover the huge possible unhappy paths that exist outside the spec green area. It is rare to find that testing things that shouldn't work is valuable, so they don't do it; but when things go wrong, it can still be a helpful early warning sign.
Type and correctness
If the test is a single point in the probability space where the program may perform an operation, the type represents the category that divides the entire part from the total probability space. We can visualize them as rectangles:
We choose a rectangle to compare the diamonds representing the program, because no type system itself can use types to fully describe our program behavior. (To give a simple example, an id that should always be a positive integer is a numeric type, but numeric types also accept fractions and negative numbers. Apart from very simple union of numeric literals, it is impossible to limit numeric types to a specific range.)
Types act as a constraint on where the program can run when you write your code. If our program starts to exceed the specified boundary of the program type, our type checker (such as TypeScript or Flow) will simply refuse to let us compile the program. This is good because in a dynamic language like JavaScript, it's easy to accidentally create a crash program that is certainly not the one you want to create. The easiest value added is automatic null value checking. If foo does not have a method named bar, calling foo.bar() will result in the well-known undefined is not a function runtime exception. If foo is fully typed, you can catch this issue by the type checker at the time of writing and point out the problematic lines of code (and with the accompanying benefits of autocomplete). This is something that testing simply cannot do.
We may want to write strict types for our programs as if we were trying to write as small as possible rectangles that still conform to our specifications. However, this has a learning curve, because leveraging the type system involves learning completely new syntax and operators as well as the syntax of generic type logic that is necessary to simulate the full dynamic range of JavaScript. Manuals and cheat sheets help lower the learning curve, and more investment is needed here.
Fortunately, this adoption/learning curve doesn't have to stop us. Since type checking is an optional process for Flow, and the configurable stringency of TypeScript (the ability to selectively ignore the problematic lines of code), we can choose from within the scope of type safety. We can even model this:
Larger rectangles, like the big red rectangle in the above image, indicate very loose adoption of the type system of the code base—for example, allowing implicitAny and relying entirely on type inference to simply limit our program from the worst coding.
Medium stringency (like medium-sized green rectangles) can indicate more faithful typeification, but there are a lot of vulnerabilities, such as using explicit any instances and manual type assertions throughout the code base. Nevertheless, even with such lightweight typing, the possible surface area of effective programs that do not match our specifications will be greatly reduced.
Maximum rigorousness, like purple rectangles, makes things so tight that it sometimes finds unconforming parts of your program (those are usually unplanned errors in program behavior). Looking for errors in existing programs like this is a very common case in teams that convert a normal JavaScript code base. However, getting the maximum type safety from our type checker may require leveraging generic types and special operators designed to refine and narrow down possible type spaces for each variable and function.
Note that we don't have to write programs before writing types. After all, we just want our types to closely simulate our specifications, so we can actually write our types first and then populate the implementation later. In theory, this would be type-driven development ; in practice, few people actually develop this way, because types penetrate and intertwin with our actual program code.
Put them together
What we ultimately want to build is an intuitive visualization that illustrates how types and tests complement each other in ensuring the correctness of the program.
Our tests assert that our program is specifically performed as expected in the selected critical path (although, as mentioned above, there are certain other test variants, the vast majority of tests do so). In the visual languages we have developed, they "fix" the program's dark green rhombus to the canonical light green rhombus. Any movement of the program will destroy these tests, which will cause them to alert. it's great! Testing also has unlimited flexibility and configurability for the most customized use cases.
Our type asserts that our program will not be out of our control by forbidding possible failure modes beyond the boundaries we draw, hoping to surround our specification as closely as possible. In our visual language, they "contain" the possible drift of programs that deviate from the norm (because we always have flaws, and every mistake we make adds additional failures to our programs). Types are also blunt, but powerful (because of type inference and editor tools) tools that benefit from a strong community that provides types that you don't have to write from scratch.
in short:
- Testing is best at ensuring that the happy path works.
- Types are best at preventing unhappy paths from existing.
Use them together according to their strengths for the best results!
If you want to learn more about how types and tests cross over, Gary Bernhardt’s great speech on Boundaries and Kent C. Dodds’ Test Trophy has had a significant impact on my thinking about this post.
The above is the detailed content of Types or Tests: Why Not Both?. For more information, please follow other related articles on the PHP Chinese website!

Custom cursors with CSS are great, but we can take things to the next level with JavaScript. Using JavaScript, we can transition between cursor states, place dynamic text within the cursor, apply complex animations, and apply filters.

Interactive CSS animations with elements ricocheting off each other seem more plausible in 2025. While it’s unnecessary to implement Pong in CSS, the increasing flexibility and power of CSS reinforce Lee's suspicion that one day it will be a

Tips and tricks on utilizing the CSS backdrop-filter property to style user interfaces. You’ll learn how to layer backdrop filters among multiple elements, and integrate them with other CSS graphical effects to create elaborate designs.

Well, it turns out that SVG's built-in animation features were never deprecated as planned. Sure, CSS and JavaScript are more than capable of carrying the load, but it's good to know that SMIL is not dead in the water as previously

Yay, let's jump for text-wrap: pretty landing in Safari Technology Preview! But beware that it's different from how it works in Chromium browsers.

This CSS-Tricks update highlights significant progress in the Almanac, recent podcast appearances, a new CSS counters guide, and the addition of several new authors contributing valuable content.

Most of the time, people showcase Tailwind's @apply feature with one of Tailwind's single-property utilities (which changes a single CSS declaration). When showcased this way, @apply doesn't sound promising at all. So obvio

Deploying like an idiot comes down to a mismatch between the tools you use to deploy and the reward in complexity reduced versus complexity added.


Hot AI Tools

Undresser.AI Undress
AI-powered app for creating realistic nude photos

AI Clothes Remover
Online AI tool for removing clothes from photos.

Undress AI Tool
Undress images for free

Clothoff.io
AI clothes remover

Video Face Swap
Swap faces in any video effortlessly with our completely free AI face swap tool!

Hot Article

Hot Tools

VSCode Windows 64-bit Download
A free and powerful IDE editor launched by Microsoft

ZendStudio 13.5.1 Mac
Powerful PHP integrated development environment

MantisBT
Mantis is an easy-to-deploy web-based defect tracking tool designed to aid in product defect tracking. It requires PHP, MySQL and a web server. Check out our demo and hosting services.

Notepad++7.3.1
Easy-to-use and free code editor

mPDF
mPDF is a PHP library that can generate PDF files from UTF-8 encoded HTML. The original author, Ian Back, wrote mPDF to output PDF files "on the fly" from his website and handle different languages. It is slower than original scripts like HTML2FPDF and produces larger files when using Unicode fonts, but supports CSS styles etc. and has a lot of enhancements. Supports almost all languages, including RTL (Arabic and Hebrew) and CJK (Chinese, Japanese and Korean). Supports nested block-level elements (such as P, DIV),
