Why Your Pipeline Needs Tests and Scans Before It's Too Late
You just finished building your application. The build succeeded. The artifact exists. Now what?
Many teams stop here. They assume that if the code compiles and the build passes, the artifact is ready for production. But a successful build only tells you that the code can be assembled. It doesn't tell you whether the code actually works, whether it has security holes, or whether it will crash when it talks to the database.
Skipping checks at this stage is like shipping a package without looking inside. You might be sending something broken, dangerous, or both.
Start With the Fastest Feedback: Unit Tests
The first check in any pipeline should be the unit tests. These tests verify the behavior of your code from the inside out. You call a function, a use case, or an endpoint, and you check whether the result matches what you expect.
The following flowchart shows the order of checks and the critical decision points where a failure stops the pipeline:
Unit tests execute your actual logic layers, from the entry point down to the deepest module. They don't need a real database, external services, or network calls. That's what makes them fast. A good unit test suite runs in seconds or a few minutes at most.
Here is a minimal YAML pipeline snippet that runs unit tests and a vulnerability scan, stopping the pipeline if either fails:
jobs:
test-and-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install dependencies
run: npm ci
- name: Run unit tests
run: npm test
- name: Run vulnerability scan
run: npm audit --audit-level=high
If a unit test fails, stop the pipeline immediately. There is no point in continuing if the basic behavior of your code is broken. Everything else depends on the assumption that the code does what it's supposed to do. If that assumption is false, every subsequent check is wasted effort.
Check If the Pieces Actually Fit: Integration Tests
Unit tests prove that meaningful behavior works while the outside world is controlled. Your internal layers can still run together, but neighboring systems are simulated or kept under test control. Software also breaks when it has to talk to real dependencies. That's where integration tests come in.
Integration tests check whether your modules can cooperate correctly. Can the user module save data to the database? Does the API respond properly when the payment service is down? Do the data formats match between services?
These tests are slower than unit tests because they need real infrastructure: a database, a message queue, or a test instance of another service. But this is exactly where most real-world bugs hide. Code that passes every unit test can still fail integration tests because of:
- Wrong database connection strings
- Mismatched data schemas
- Incorrect configuration values
- Missing environment variables
Integration tests catch the kind of problems that only appear when components actually touch each other. If you skip them, you're betting that your code will work perfectly in an environment you haven't tested.
Scan the Code Itself: Static Analysis
Functional tests check what the code does. Static analysis checks how the code is written. It reads your source code without executing it and looks for problematic patterns.
Static analysis tools can detect:
- Variables that are declared but never used
- Code that is too complex or deeply nested
- Potential null pointer dereferences
- Violations of coding standards your team agreed on
- Security-sensitive patterns like hardcoded credentials
Static analysis won't catch logic bugs. But it catches the kind of mistakes that developers make dozens of times a day and often miss during code review. It also enforces consistency across the team. When every commit is checked against the same rules, the codebase stays maintainable even as the team grows.
Find the Hidden Dangers: Vulnerability Scanning
Most security vulnerabilities in modern applications don't come from code you wrote. They come from the libraries and packages you depend on. A single outdated dependency with a known exploit can compromise your entire application.
Vulnerability scanning checks your dependency list against databases of known security issues. If a library has a critical vulnerability, the scanner flags it and the pipeline should stop. Better to delay a release than to ship a known security hole to production.
This scan should run on every build, not just before major releases. New vulnerabilities are discovered every day. A library that was safe last week might have a critical CVE published today. Regular scanning ensures you catch these issues before they reach users.
Keep the Evidence: Why Results Must Be Saved
Every check in this stage produces results. Unit tests pass or fail. Integration tests report which scenarios worked. Static analysis lists warnings and errors. Vulnerability scans flag dependencies.
These results are evidence. They prove that the pipeline performed its checks and show what happened. Save them.
Evidence matters for three reasons:
Debugging production issues. When something goes wrong in production, you can check whether the pipeline detected the problem. If it did, you know the check worked. If it didn't, you know you need a better test.
Audit and compliance. Regulators, customers, and internal policies often require proof that every change was tested before release. Saved evidence satisfies that requirement.
Trend analysis. Over time, evidence shows whether your code quality is improving. Are tests failing less often? Are vulnerabilities decreasing? Are certain modules consistently problematic? This data helps you decide where to invest your improvement efforts.
Store evidence in a format that machines can read, like JUnit XML or SARIF, and also keep a human-readable summary. Put it in a place that survives after the pipeline finishes, like an artifact registry or a dedicated storage bucket. Don't rely on pipeline logs that get cleaned up after a few days.
Practical Checklist for This Stage
Before you move an artifact to deployment, confirm these checks are in place:
- Unit tests run on every commit and fail the pipeline if broken
- Integration tests cover critical component interactions
- Static analysis enforces code quality standards
- Vulnerability scanning checks all dependencies
- All results are saved as evidence with timestamps and commit IDs
What Happens Next
After tests and scans pass, you know the artifact is worth keeping. It works correctly, the code is maintainable, and the dependencies are safe. Now you need to package it and store it so it can be deployed later.
But if any check fails, the pipeline stops. The team gets notified. The artifact never reaches the next stage. That's the point: catch the problem here, not in production.
The cost of finding a bug in production is exponentially higher than finding it in the pipeline. A failed test costs a few minutes of developer time. A production outage costs user trust, incident response, and late-night debugging sessions.
Test and scan early. Test and scan automatically. And keep the evidence. Your future self will thank you when something goes wrong and you can prove exactly what was checked and when.