AI Software Testing: How Machine Learning Finds Bugs Early

Photo: cottonbro studio / Pexels
The Consortium for Information & Software Quality (CISQ) estimates that poor software quality cost the United States at least $2.41 trillion in 2022, and testing is the main defense against that bill. The problem is that software grows faster than any team's ability to test it by hand. Machine learning attacks the problem from four directions: it chooses which tests to run, steers fuzzers toward inputs that crash programs, writes test code, and watches production systems for behavior no test anticipated. This article walks through each, with the published numbers and the failures.
Key takeaways
- Predictive test selection at Meta cut the infrastructure cost of testing code changes by a factor of two while still reporting over 95% of individual test failures and over 99.9% of faulty changes.
- Neural fuzzing is real but incremental: the NEUZZ research system found 31 previously unknown bugs in 10 real programs and reached 3 times the edge coverage of the fuzzers it was compared with over 24 hours.
- LLM-written tests need a filter. In Meta's TestGen-LLM study, 75% of generated test cases built, 57% passed reliably, and only 25% increased coverage.
- Google's Big Sleep agent found a real SQLite vulnerability before release in 2024, yet its authors said a target-specific fuzzer is likely at least as effective today.
- AI testing tools shift human effort rather than remove it. Someone still has to decide what correct behavior is.
Why testing is a search problem
A test is a question asked of a program: given this input, does the output match what we expect? A mature codebase has hundreds of thousands of such questions, and the space of possible inputs is effectively infinite. Testing therefore resembles sweeping a beach with a metal detector. You cannot dig up every grain of sand, so you decide where to sweep and how carefully, and you accept that some coins will be missed.
Machine learning helps with the sweeping. It does not make the beach smaller, but it can learn where coins have been found before. Each of the techniques below applies that idea to a different part of the process: picking tests, generating inputs, writing test code, and monitoring live systems.
The oracle problem
Every automated test needs an oracle, a way to know what the right answer is. For add(2, 3) the oracle is easy: 5. For a function that renders a web page, ranks search results, or processes a refund under a complicated policy, the correct output may be unclear even to the engineers who wrote it. Software testing researchers call this the test oracle problem, and it is the reason no tool can simply "find all the bugs".
The oracle problem explains why ML-generated tests are a double-edged tool. A model that reads a function and writes tests for it may encode what the function currently does rather than what it should do. That is like grading your own exam against your own answers: you will score well and learn nothing. Most of the work in industrial AI testing goes into filtering out this failure.
Choosing which tests to run
Continuous integration (CI) is the practice of running an automated test suite every time a developer proposes a change. In a large company a single repository receives thousands of changes a day, and running every test on every change is too slow and too expensive. Two classic responses are test selection (run only a subset) and test prioritization (run everything but start with the tests most likely to fail).
Meta's engineers described a machine-learning version of selection in a 2018 paper titled "Predictive Test Selection," by Mateusz Machalica, Alex Samylkin, Meredith Porth, and Satish Chandra. The system learns from a large dataset of historical test outcomes and predicts, for each change, which tests are likely to detect a problem. It also accounts for flaky tests, which are tests that pass or fail non-deterministically without any change in the code. According to the paper, the approach cuts the total infrastructure cost of testing changes by a factor of two while still reporting over 95% of individual test failures and over 99.9% of faulty changes back to developers.
The two percentages measure different things. "Individual test failures" counts every failing test, so a change that breaks 10 tests counts as 10 events. "Faulty changes" counts each bad change once, so the system only has to surface one failing test per bad change for the developer to learn something is wrong. That is why the second number is much higher: a selection model can miss several tests that fail for the same underlying reason and still tell the developer what they need to know.
What the model learns from
Selection models use features that describe the relationship between a change and a test. Typical inputs include which files a change touched, how often a test has failed in the past when those files changed, how recently the test last failed, and how long the test takes. The learning algorithms in published industrial work tend to be modest rather than exotic, for example gradient-boosted decision trees, a method covered in XGBoost explained, because the data is tabular and the decision must be cheap enough to run on every commit.
| Approach | What it does | Strength | Risk |
|---|---|---|---|
| Test selection | Runs only tests predicted to matter for a change | Large savings in compute and wait time | A missed test can let a bug through |
| Test prioritization | Orders the whole suite by predicted failure likelihood | No test is skipped, failures surface earlier | Total compute does not fall |
| Flaky test management | Predicts which failures are noise | Fewer false alarms and reruns | A real failure may be dismissed as flakiness |
The practical lesson is that selection works best when the cost of a missed bug is cushioned by later stages. A change that slips past the fast, selected suite can still be caught by a full nightly run or by fuzzing, which is why companies treat ML selection as a filter at the front of a pipeline rather than the only barrier.
Fuzzing: throwing garbage at programs on purpose
Fuzzing is a testing technique that feeds a program unexpected, malformed, or random inputs and watches for crashes, hangs, and memory errors. It was invented in an academic setting. According to Professor Barton Miller's fuzz-testing page at the University of Wisconsin-Madison, he assigned fuzzing as a project in a graduate operating systems class in 1988, and the 1990 paper "An Empirical Study of the Reliability of UNIX Utilities" by Miller, Fredriksen, and So followed.
The idea has not aged out. In a 2020 paper, "The Relevance of Classic Fuzz Testing: Have We Solved This One?", Miller, Mengxiao Zhang, and Elisa Heymann reran updated fuzzing tools against standard command-line utilities. They found that 9 of 74 utilities on Linux (about 12%), 15 of 78 on FreeBSD (about 19%), and 12 of 76 on macOS (about 16%) crashed or hung, with 24 different utilities failing across the three systems. The failure rates were higher than in the authors' earlier studies from 1995, 2000, and 2006, and the few utilities written in Rust were no more reliable than the others.
Coverage-guided fuzzing and where ML fits in
Modern fuzzers such as AFL (American Fuzzy Lop) and libFuzzer are coverage-guided. They instrument a program to record which branches each input executes, keep inputs that reach new branches, and mutate those inputs to explore further. This turns blind randomness into an evolutionary search: inputs that find new code are rewarded and bred.
The weakness is that program behavior is full of cliffs. A byte flip rarely turns a valid file into a different valid file, because checksums, length fields, and syntax rules reject most mutations. Climbing in this landscape is like hiking up a hill in thick fog, where you can only feel the ground under your feet and cannot see the summit.
NEUZZ, by Dongdong She, Kexin Pei, Dave Epstein, Junfeng Yang, Baishakhi Ray, and Suman Jana, tried to give the fuzzer a map. The system trains a neural network to approximate a program's branching behavior, which smooths out the cliffs, and then uses gradient information from that surrogate to decide which input bytes to change. The paper was submitted to arXiv in July 2018 and accepted to the 40th IEEE Symposium on Security and Privacy in 2019. Its abstract reports that NEUZZ outperformed 10 state-of-the-art graybox fuzzers on 10 real-world programs, found 31 unknown bugs that the other fuzzers missed, and achieved 3 times more edge coverage over a 24-hour run. For a primer on the gradient method the surrogate relies on, see how gradient descent finds better answers.
The limits of these results are clear. NEUZZ is a research system evaluated on a defined set of programs. Its results show that ML guidance can help on that benchmark, not that neural fuzzers have replaced the standard tools in production.
Fuzzing at industrial scale
The largest production fuzzing systems rely mostly on coverage-guided engines rather than neural networks. Google's ClusterFuzz, according to its project page, is a scalable fuzzing infrastructure that runs fuzzers, deduplicates crashes, files bugs, and closes them automatically once fixed. As of February 2023 it had found about 27,000 bugs in Google's own products, including Chrome, and through OSS-Fuzz, over 8,900 vulnerabilities and 28,000 bugs across 850 open source projects. The OSS-Fuzz project page, updated by August 2023, reports more than 10,000 vulnerabilities and more than 36,000 bugs across about 1,000 projects. OSS-Fuzz launched in 2016 after the Heartbleed vulnerability.
These figures are often attributed to machine learning, but they are results of large-scale, automated, coverage-guided fuzzing. That distinction matters when evaluating vendor claims: the infrastructure that runs fuzzers continuously, triages crashes, and tracks fixes accounts for much of the value, and it does not depend on a neural network.
Large language models as test writers
Large language models (LLMs), the neural networks behind systems such as ChatGPT and Claude, are trained on huge amounts of source code, which makes them natural candidates for writing tests. If you want the mechanics, how large language models work covers the architecture, and why AI hallucinates facts explains why generated code can look right and be wrong.
What Meta measured
The clearest public evidence comes from Meta. In February 2024 a team led by Nadia Alshahwan and Mark Harman published "Automated Unit Test Improvement using Large Language Models at Meta," describing a tool called TestGen-LLM. The tool does not ship whatever the model writes. It generates candidate tests and then applies a series of filters: does the test build, does it pass reliably when run repeatedly, and does it increase code coverage over the existing tests? Only candidates that clear all three are proposed to engineers.
In an evaluation on Instagram Reels and Stories, the paper reports that 75% of TestGen-LLM's test cases built correctly, 57% passed reliably, and 25% increased coverage. During test-a-thons on Instagram and Facebook it improved 11.5% of the classes it was applied to, and 73% of its recommendations were accepted for production by Meta's engineers. Notice the funnel. Starting from everything the model generates, roughly three out of four tests survive compilation, but only one in four adds coverage. Without the filters, most of the output would be noise.
In January 2025 a Meta team published a follow-up, "Mutation-Guided LLM-based Test Generation at Meta," describing a system called ACH. It targets a different question: rather than raising coverage, can the model write tests that catch a specific kind of fault, such as a privacy bug? To do this it uses mutation testing, in which small faults are deliberately injected into code, and a test suite is judged by whether it notices. If a mutant survives, the tests have a gap, and the LLM is asked to write a test that kills it.
The paper reports that the team analyzed 10,795 Android Kotlin classes across seven Meta platforms, generated 9,095 mutants, and produced 571 privacy-hardening test cases. A hard part is equivalent mutants, changes that do not alter behavior and so can never be caught by any test. An LLM-based equivalent mutant detector had a precision of 0.79 and a recall of 0.47, rising to 0.95 and 0.96 with simple preprocessing. Engineers accepted 73% of generated tests in Messenger and WhatsApp test-a-thons, and 36% were judged privacy-relevant.
Generating fuzz targets and finding vulnerabilities
A fuzzer needs a harness, a small program that feeds its inputs into a particular function or library. Writing harnesses by hand is slow, so Google's OSS-Fuzz-Gen project uses LLMs to write them. Its public page reports that generated targets have found 30 new bugs and vulnerabilities, including OpenSSL CVE-2024-9143, an out-of-bounds read and write. In a January 31, 2024 sample run on more than 1,300 benchmarks from 297 projects, the tool produced valid targets with a coverage increase for 160 C and C++ projects, with a maximum line-coverage increase of 29% over the existing human-written targets. The page cautions that extra code compiled into a new target can inflate percentage gains.
A related project, Big Sleep, a collaboration between Google Project Zero and Google DeepMind, went further. In a November 1, 2024 post, the team described an agent that found an exploitable stack buffer underflow in SQLite. It had been given a recent commit and asked to look for similar bugs that remained, a task called variant analysis. The authors reported the issue in early October 2024 and it was fixed the same day, before an official release, so SQLite users were not affected.
The authors were careful about the claim. They called the results "highly experimental," noted that their evaluation relied on small programs with known vulnerabilities, and stated that a target-specific fuzzer is, at present, likely at least as effective at finding vulnerabilities. In one experiment the AFL fuzzer ran for 150 CPU-hours on the same code without finding the bug, which they attributed in part to the harness configurations used by existing setups.
How good are models at fixing real bugs?
Writing a test and fixing a bug are different skills. SWE-bench, introduced in October 2023 by Carlos Jimenez and colleagues, tests language models on 2,294 software engineering tasks taken from real GitHub issues across 12 popular Python repositories. In the original paper the best model evaluated, Claude 2, resolved just 1.96% of the issues. Model capabilities have moved quickly since then, so that number is a snapshot of 2023 rather than a current limit, but the benchmark illustrates how hard real repositories are compared with isolated functions. For the broader context on how such benchmarks are built and where they mislead, see LLM evaluation explained.
Search-based testing and automated repair
Not every useful technique involves neural networks. In 2018 Facebook (now Meta) described two tools in production. Sapienz is an automated testing tool for Android apps that uses search-based techniques to explore the user interface. According to the company's engineering blog, it produces hundreds of monthly bug reports that pinpoint the lines at fault, and about three quarters of its reports lead to fixes by developers. SapFix takes the next step by proposing patches for bugs that Sapienz finds, using three strategies: reverting the code change that introduced a crash, applying templates harvested from past human fixes, and making small mutations to the syntax tree of the crashing statement.
The design point is that SapFix generates several candidate patches, checks each for compilation errors and for whether the crash persists or new crashes appear, and requires human approval before a patch lands. That pattern, machine-generated candidates verified by machines and approved by humans, recurs across every deployment described in this article. For a game-industry application of the same ideas, see AI-powered game testing.
Watching production for what tests missed
No test suite predicts every failure that real traffic will find. Production monitoring therefore uses statistical models to flag unusual behavior in latency, error rates, or resource use, and some teams train classifiers to decide which anomalies are likely to affect users. The challenge is the balance between sensitivity and noise: a monitor that pages an engineer every time a metric wiggles will soon be ignored.
Two related pieces cover the surrounding practice. AI observability explained describes how teams monitor models and agents once they are live, and what model drift means in production explains why a model that tests well can degrade as the data it sees changes. Teams building such systems often rely on the practices described in MLOps for beginners. For the security side of anomaly detection, see machine learning in cybersecurity.
Where AI testing fails
The research above also documents the failure modes, and they follow a pattern.
- Tests that bless bugs. An LLM reading a buggy function may produce tests that assert the buggy behavior. Filters for compilation and reliability do not catch this, because the test passes. Coverage filters do not catch it either. Only a human, or a specification, knows what the function was meant to do.
- Coverage is not quality. A test can execute a line without checking anything about it. Mutation testing, as in Meta's ACH work, is a stronger signal because it asks whether a test notices a change.
- Flaky results. Tests that fail at random erode trust. Meta's selection work accounts for flakiness explicitly because a model trained on noisy labels learns noise.
- Evaluation leakage. If a model has seen the code or the tests during training, results on benchmarks overstate real-world performance. See why data leakage breaks AI evaluations.
- Rare events. Bugs are rare, so a model that predicts "no failure" for everything looks accurate. Metrics such as precision and recall matter more than accuracy, as explained in precision, recall, and class imbalance.
- Security of the tool itself. Agents that read untrusted code can be manipulated by instructions hidden in that code, a risk covered in prompt injection explained.
Comparing the techniques
| Technique | What the ML does | Published evidence | Main limit |
|---|---|---|---|
| Predictive test selection | Predicts which tests a change will break | Meta: half the infrastructure cost, over 99.9% of faulty changes reported | Needs large history; risk of missing a test |
| Neural-guided fuzzing | Approximates program behavior to steer mutations | NEUZZ: 31 unknown bugs, 3 times edge coverage in 24 hours | Research benchmarks; harder to scale |
| Coverage-guided fuzzing at scale | Mostly non-ML feedback loops | OSS-Fuzz: over 10,000 vulnerabilities, 36,000 bugs by August 2023 | Needs harnesses and maintenance |
| LLM unit-test generation | Drafts candidate tests, filtered by builds and coverage | TestGen-LLM: 75% build, 57% reliable, 25% add coverage | Tests may assert wrong behavior |
| LLM fuzz-target generation | Writes harnesses for fuzzers | OSS-Fuzz-Gen: 30 new bugs including CVE-2024-9143 | Coverage gains can be inflated |
| Agentic vulnerability research | Reasons about code and variants | Big Sleep: SQLite stack buffer underflow found before release | "Highly experimental" by its authors |
| Search-based testing and repair | Explores app states and proposes patches | Sapienz: about three quarters of reports led to fixes | Human approval still required |
Measuring whether a tool is working
Adopting any of these tools is easier to justify with measurements than with demonstrations. Engineers who evaluate them commonly track a small set of outcomes.
- Escaped defects: bugs found in production that tests should have caught.
- Time to first failure: how soon after a change a broken test reports it.
- Compute cost per change: the savings that selection promises.
- Flake rate: the share of failures that disappear on rerun.
- Mutation score: the fraction of injected faults that the test suite detects.
A tool that raises coverage but leaves the mutation score unchanged is producing tests that look good and check little. A tool that lowers compute cost but raises escaped defects is trading bugs for savings. Seeing both curves together is the only way to tell.
Frequently asked questions
Q: Can AI replace QA engineers? A: No published evidence supports that. The deployments described above keep humans in the loop: engineers accepted 73% of TestGen-LLM's recommendations after review, and SapFix requires human approval of patches. AI shifts effort toward reviewing and specifying behavior, while people still decide what counts as correct.
Q: What is the difference between test selection and test prioritization? A: Selection runs only the tests a model predicts are relevant to a change and skips the rest. Prioritization runs the full suite but orders it so that likely failures run first. Selection saves compute, as in Meta's reported factor-of-two cost reduction, while prioritization mostly shortens time to the first failure.
Q: How does fuzzing differ from ordinary unit testing? A: A unit test checks a specific input against a specific expected output written by a person. A fuzzer generates enormous numbers of inputs automatically and watches for crashes, hangs, or memory errors, which need no expected output. That makes fuzzing good at security-relevant bugs and poor at checking business logic.
Q: Are LLM-generated tests safe to merge without review? A: The evidence says no. Meta's TestGen-LLM only proposes tests that build, pass reliably, and increase coverage, and even then engineers reviewed the output. A passing test can still encode wrong behavior, so someone must confirm that each assertion reflects what the code should do.
Q: What is mutation testing and why does it matter for AI? A: Mutation testing injects small faults into code and checks whether the tests fail. Surviving mutants reveal weak tests. Meta's ACH system uses it to direct an LLM toward writing tests that kill specific mutants, which gives a more meaningful target than raw coverage.
Q: Where should a team start with ML-based testing? A: Evidence from industry favors the least invasive options first: use existing history to rank or select tests, add continuous fuzzing for components that parse untrusted input, and treat LLM-written tests as proposals that must pass filters and review. Measure escaped defects and flake rate before and after.
The takeaway
The common thread in the published results is that machine learning works best as a search accelerator inside a process that already has strong checks. Meta's selection model is safe to deploy because later stages still run. TestGen-LLM is useful because most of what the model produces is thrown away by builds, reliability reruns, and coverage checks. Big Sleep's SQLite find is notable because a human team set up the task and verified the result.
What remains unsolved is the oracle problem. A tool can find where code crashes, and it can generate thousands of plausible tests, but nobody except a person or a specification can say what the software is supposed to do. The teams that benefit most from AI testing will be the ones that write down expected behavior clearly enough for any tool, human or machine, to check against it.
Sources
- CISQ, "The Cost of Poor Software Quality in the US: A 2022 Report": https://www.it-cisq.org/the-cost-of-poor-quality-software-in-the-us-a-2022-report/
- Machalica, Samylkin, Porth, Chandra, "Predictive Test Selection" (arXiv:1810.05286): https://arxiv.org/abs/1810.05286
- She et al., "NEUZZ: Efficient Fuzzing with Neural Program Smoothing" (arXiv:1807.05620): https://arxiv.org/abs/1807.05620
- Miller, Zhang, Heymann, "The Relevance of Classic Fuzz Testing: Have We Solved This One?" (arXiv:2008.06537): https://arxiv.org/abs/2008.06537
- Barton Miller, Fuzz Testing of Application Reliability, University of Wisconsin-Madison: https://pages.cs.wisc.edu/~bart/fuzz/
- Google ClusterFuzz repository: https://github.com/google/clusterfuzz
- OSS-Fuzz documentation: https://google.github.io/oss-fuzz/
- Google OSS-Fuzz-Gen repository: https://github.com/google/oss-fuzz-gen
- Alshahwan et al., "Automated Unit Test Improvement using Large Language Models at Meta" (arXiv:2402.09171): https://arxiv.org/abs/2402.09171
- Foster et al., "Mutation-Guided LLM-based Test Generation at Meta" (arXiv:2501.12862): https://arxiv.org/abs/2501.12862
- Google Project Zero, "From Naptime to Big Sleep" (November 1, 2024): https://projectzero.google/2024/10/from-naptime-to-big-sleep.html
- Jimenez et al., "SWE-bench: Can Language Models Resolve Real-World GitHub Issues?" (arXiv:2310.06770): https://arxiv.org/abs/2310.06770
- Meta Engineering, "Finding and fixing software bugs automatically with SapFix and Sapienz" (September 13, 2018): https://engineering.fb.com/2018/09/13/developer-tools/finding-and-fixing-software-bugs-automatically-with-sapfix-and-sapienz/
Read next:
Follow new articles
Add SciHub101 to your feed reader to see new science and technology stories.
About the Author
SciHub101 is an independent science and technology publication founded and edited by Buğra Sözeri, a software engineer and app developer based in Türkiye. AI tools assist with drafting and research. Source documentation in the existing archive is uneven, and articles are being reviewed and corrected. The Editorial Team byline identifies editorial responsibility; it does not certify specialist or independent human review. Where we compare products, anything built by our own team is labelled as such — see the editorial guidelines and About page for how corrections and disclosures work.
Related Articles

History of Electricity: Faraday, Tesla, Edison, and the Force That Powers the World
Electricity was a scientific curiosity in 1800. Within 100 years it was lighting cities and running factories.

How the Transistor Changed Everything
Demonstrated at Bell Labs in December 1947, the transistor became the building block of every digital device on earth.

Reinforcement Learning for Robotics: Teaching Machines to Move Through the World
Robots now learn walking, balancing, and some manipulation by trial and error, mostly in simulation. Here is how the training works, what has reached real hardware, and what still breaks.

Neural Networks in Network Routing: How AI Optimizes 5G and Beyond
AI-driven network routing is replacing static algorithms in 5G—reducing latency, balancing load, and adapting to traffic in real time.

Machine Learning in Cybersecurity: How AI Detects Intrusions Before Humans Can
ML-powered intrusion detection systems now identify novel attacks in milliseconds—analyzing network patterns no human analyst could process.

How AI Designs Chips: Hardware-Aware Neural Architecture Search
AI now helps design the chips that run AI. Reinforcement-learning floorplanning, neural architecture search, and co-design are changing semiconductor R&D, and parts of the evidence are disputed.