<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Mauricio Choqueña Choque</title>
    <description>The latest articles on DEV Community by Mauricio Choqueña Choque (@mauchoquenachoque).</description>
    <link>https://dev.to/mauchoquenachoque</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4016517%2F7b74a193-2cd7-4965-af8a-6349fe4571b9.png</url>
      <title>DEV Community: Mauricio Choqueña Choque</title>
      <link>https://dev.to/mauchoquenachoque</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mauchoquenachoque"/>
    <language>en</language>
    <item>
      <title>Applying SAST Tools to Infrastructure as Code — A Hands-On Look at Checkov</title>
      <dc:creator>Mauricio Choqueña Choque</dc:creator>
      <pubDate>Sun, 05 Jul 2026 19:53:14 +0000</pubDate>
      <link>https://dev.to/mauchoquenachoque/applying-sast-tools-to-infrastructure-as-code-a-hands-on-look-at-checkov-18lh</link>
      <guid>https://dev.to/mauchoquenachoque/applying-sast-tools-to-infrastructure-as-code-a-hands-on-look-at-checkov-18lh</guid>
      <description>&lt;h2&gt;
  
  
  Why Infrastructure as Code Needs Static Analysis Too
&lt;/h2&gt;

&lt;p&gt;Infrastructure as Code (IaC) — Terraform, Pulumi, OpenTofu, CloudFormation — turned infrastructure into something version-controlled, reviewable, and reproducible. But that also means a single misconfigured line — an S3 bucket left public, a security group open to &lt;code&gt;0.0.0.0/0&lt;/code&gt;, an unencrypted database — can be committed, merged, and deployed automatically, at scale, before anyone notices.&lt;/p&gt;

&lt;p&gt;Static Application Security Testing (SAST) for IaC works the same way it does for application code: it parses configuration files &lt;strong&gt;without deploying anything&lt;/strong&gt;, and flags insecure patterns against a known rule set. OWASP's list of Source Code Analysis Tools includes several options for this. This article focuses on &lt;strong&gt;Checkov&lt;/strong&gt;, an open-source policy-as-code scanner built by Bridgecrew (now part of Palo Alto Networks), chosen here specifically because it's free, multi-cloud, and multi-framework — without relying on tfsec.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Checkov
&lt;/h2&gt;

&lt;p&gt;Checkov scans IaC files and builds a graph representation of the resources and their relationships, then evaluates that graph against hundreds of built-in security and compliance policies.&lt;/p&gt;

&lt;p&gt;Key features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Supports &lt;strong&gt;Terraform, OpenTofu, CloudFormation, Kubernetes manifests, Dockerfiles, Helm charts, Serverless framework, ARM templates&lt;/strong&gt;, and more — not limited to one IaC tool.&lt;/li&gt;
&lt;li&gt;Over 1,000 built-in policies covering AWS, Azure, GCP, and Kubernetes best practices (CIS Benchmarks aligned).&lt;/li&gt;
&lt;li&gt;Supports &lt;strong&gt;custom policies&lt;/strong&gt; written in Python or YAML, so teams can encode their own organizational rules.&lt;/li&gt;
&lt;li&gt;Outputs in CLI text, JSON, JUnit XML, and SARIF — SARIF format integrates directly with GitHub's Security tab.&lt;/li&gt;
&lt;li&gt;Can suppress specific findings inline with comments, without disabling the whole rule globally.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Installing and Running Checkov
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;checkov

&lt;span class="c"&gt;# Scan a directory of Terraform files&lt;/span&gt;
checkov &lt;span class="nt"&gt;-d&lt;/span&gt; ./infrastructure

&lt;span class="c"&gt;# Scan a single file&lt;/span&gt;
checkov &lt;span class="nt"&gt;-f&lt;/span&gt; main.tf

&lt;span class="c"&gt;# Output as JSON for CI pipelines&lt;/span&gt;
checkov &lt;span class="nt"&gt;-d&lt;/span&gt; ./infrastructure &lt;span class="nt"&gt;-o&lt;/span&gt; json &lt;span class="nt"&gt;--output-file-path&lt;/span&gt; ./checkov_report.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  A Real Example
&lt;/h2&gt;

&lt;p&gt;Consider this Terraform snippet — a common pattern that looks harmless but hides several real misconfigurations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_s3_bucket"&lt;/span&gt; &lt;span class="s2"&gt;"data"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;bucket&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"company-data-bucket"&lt;/span&gt;
  &lt;span class="nx"&gt;acl&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"public-read"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_security_group"&lt;/span&gt; &lt;span class="s2"&gt;"web"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"web-sg"&lt;/span&gt;

  &lt;span class="nx"&gt;ingress&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;from_port&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;22&lt;/span&gt;
    &lt;span class="nx"&gt;to_port&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;22&lt;/span&gt;
    &lt;span class="nx"&gt;protocol&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"tcp"&lt;/span&gt;
    &lt;span class="nx"&gt;cidr_blocks&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"0.0.0.0/0"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_db_instance"&lt;/span&gt; &lt;span class="s2"&gt;"app_db"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;identifier&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"app-database"&lt;/span&gt;
  &lt;span class="nx"&gt;engine&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"mysql"&lt;/span&gt;
  &lt;span class="nx"&gt;instance_class&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"db.t3.micro"&lt;/span&gt;
  &lt;span class="nx"&gt;allocated_storage&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;
  &lt;span class="nx"&gt;storage_encrypted&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="nx"&gt;publicly_accessible&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running &lt;code&gt;checkov -f main.tf&lt;/code&gt; produces output like:&lt;br&gt;
Check: CKV_AWS_20: "S3 Bucket has an ACL defined which allows public READ access."&lt;br&gt;
FAILED for resource: aws_s3_bucket.data&lt;br&gt;
File: main.tf:1-4&lt;br&gt;
Check: CKV_AWS_24: "Ensure no security groups allow ingress from 0.0.0.0:0 to port 22"&lt;br&gt;
FAILED for resource: aws_security_group.web&lt;br&gt;
File: main.tf:6-13&lt;br&gt;
Check: CKV_AWS_16: "Ensure RDS instances have storage_encrypted enabled"&lt;br&gt;
FAILED for resource: aws_db_instance.app_db&lt;br&gt;
File: main.tf:15-21&lt;br&gt;
Check: CKV_AWS_17: "Ensure RDS instances are not publicly accessible"&lt;br&gt;
FAILED for resource: aws_db_instance.app_db&lt;br&gt;
File: main.tf:15-21&lt;/p&gt;

&lt;p&gt;Each check includes a rule ID (&lt;code&gt;CKV_AWS_20&lt;/code&gt;, &lt;code&gt;CKV_AWS_24&lt;/code&gt;...), a human-readable description, the exact resource name, and the file/line range — enough for a developer to jump straight to the fix.&lt;/p&gt;
&lt;h2&gt;
  
  
  Fixing the Findings
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_s3_bucket"&lt;/span&gt; &lt;span class="s2"&gt;"data"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;bucket&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"company-data-bucket"&lt;/span&gt;
  &lt;span class="nx"&gt;acl&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"private"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_security_group"&lt;/span&gt; &lt;span class="s2"&gt;"web"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"web-sg"&lt;/span&gt;

  &lt;span class="nx"&gt;ingress&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;from_port&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;22&lt;/span&gt;
    &lt;span class="nx"&gt;to_port&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;22&lt;/span&gt;
    &lt;span class="nx"&gt;protocol&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"tcp"&lt;/span&gt;
    &lt;span class="nx"&gt;cidr_blocks&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"10.0.0.0/16"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;  &lt;span class="c1"&gt;# restricted to internal VPC range&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_db_instance"&lt;/span&gt; &lt;span class="s2"&gt;"app_db"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;identifier&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"app-database"&lt;/span&gt;
  &lt;span class="nx"&gt;engine&lt;/span&gt;              &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"mysql"&lt;/span&gt;
  &lt;span class="nx"&gt;instance_class&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"db.t3.micro"&lt;/span&gt;
  &lt;span class="nx"&gt;allocated_storage&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;
  &lt;span class="nx"&gt;storage_encrypted&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;publicly_accessible&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Re-running Checkov against the fixed file shows all four checks passing.&lt;/p&gt;
&lt;h2&gt;
  
  
  Integrating Checkov into a CI Pipeline
&lt;/h2&gt;

&lt;p&gt;A minimal GitHub Actions workflow that scans every Terraform change and fails the build on findings:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;IaC Security Scan with Checkov&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;checkov-scan&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run Checkov&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bridgecrewio/checkov-action@master&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;directory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./infrastructure&lt;/span&gt;
          &lt;span class="na"&gt;framework&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;terraform&lt;/span&gt;
          &lt;span class="na"&gt;output_format&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sarif&lt;/span&gt;
          &lt;span class="na"&gt;output_file_path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;checkov-results.sarif&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Upload SARIF to GitHub Security tab&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github/codeql-action/upload-sarif@v3&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;sarif_file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;checkov-results.sarif&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Uploading the SARIF report to GitHub's Security tab means findings show up alongside other code scanning alerts, with no separate dashboard needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Public Repository Example
&lt;/h2&gt;

&lt;p&gt;You can see Checkov applied to real Terraform code here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/bridgecrewio/checkov" rel="noopener noreferrer"&gt;github.com/bridgecrewio/checkov&lt;/a&gt;&lt;/strong&gt; — the tool's own repository, which includes hundreds of example Terraform/CloudFormation/Kubernetes files used as test fixtures for its own rule set, under &lt;code&gt;tests/terraform/checks&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/bridgecrewio/terragoat" rel="noopener noreferrer"&gt;github.com/bridgecrewio/terragoat&lt;/a&gt;&lt;/strong&gt; — a deliberately vulnerable, multi-cloud Terraform application built specifically to be scanned by tools like Checkov, useful as a public sandbox to practice against realistic misconfigurations.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Cloning either repo and running &lt;code&gt;checkov -d .&lt;/code&gt; locally demonstrates the tool against real, non-trivial infrastructure code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Checkov Fits Among IaC SAST Tools
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;IaC scope&lt;/th&gt;
&lt;th&gt;License&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Checkov&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Terraform, CloudFormation, Kubernetes, Helm, Dockerfile, ARM, Serverless&lt;/td&gt;
&lt;td&gt;Open source (Apache 2.0)&lt;/td&gt;
&lt;td&gt;Broadest framework coverage, graph-based analysis&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Terrascan&lt;/td&gt;
&lt;td&gt;Terraform, Kubernetes, Helm&lt;/td&gt;
&lt;td&gt;Open source&lt;/td&gt;
&lt;td&gt;OPA/Rego-based policies&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;KICS (Checkmarx)&lt;/td&gt;
&lt;td&gt;Terraform, CloudFormation, Kubernetes, Docker, Ansible&lt;/td&gt;
&lt;td&gt;Open source&lt;/td&gt;
&lt;td&gt;Large community rule library&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cloud Custodian&lt;/td&gt;
&lt;td&gt;Cloud resources (runtime, not pure static)&lt;/td&gt;
&lt;td&gt;Open source&lt;/td&gt;
&lt;td&gt;More focused on ongoing compliance than pre-deploy scanning&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Compared to single-framework scanners, Checkov's advantage is breadth: one tool can cover Terraform, Kubernetes manifests, and Dockerfiles in the same pipeline run, which reduces the number of separate security tools a team needs to maintain.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Securing infrastructure code doesn't require a heavyweight commercial platform to get started. An open-source, policy-as-code scanner like Checkov can catch real, high-impact misconfigurations — public buckets, open security groups, unencrypted databases — before &lt;code&gt;terraform apply&lt;/code&gt; ever runs, and it does so across multiple IaC frameworks at once. For teams adopting DevSecOps practices, plugging a tool like this into CI is a low-cost, high-value first step toward "shifting left" on cloud security.&lt;/p&gt;

</description>
      <category>iac</category>
      <category>terraform</category>
      <category>security</category>
      <category>devsecops</category>
    </item>
    <item>
      <title>Applying SAST Tools to Real Applications — A Hands-On Look at Bandit</title>
      <dc:creator>Mauricio Choqueña Choque</dc:creator>
      <pubDate>Sun, 05 Jul 2026 19:51:15 +0000</pubDate>
      <link>https://dev.to/mauchoquenachoque/applying-sast-tools-to-real-applications-a-hands-on-look-at-bandit-1lab</link>
      <guid>https://dev.to/mauchoquenachoque/applying-sast-tools-to-real-applications-a-hands-on-look-at-bandit-1lab</guid>
      <description>&lt;h2&gt;
  
  
  What is SAST, and Why It Matters
&lt;/h2&gt;

&lt;p&gt;Static Application Security Testing (SAST) analyzes source code — without executing it — to find security vulnerabilities before they ever reach production: hardcoded secrets, SQL injection risks, use of insecure functions, weak cryptography, and more. Unlike dynamic testing (DAST), which probes a running application, SAST catches problems at the earliest possible stage: while the code is being written or committed.&lt;/p&gt;

&lt;p&gt;OWASP maintains a well-known list of Source Code Analysis Tools covering many languages and licensing models. This article focuses on &lt;strong&gt;Bandit&lt;/strong&gt;, an open-source SAST tool built specifically for Python codebases, chosen here because it's lightweight, free, and easy to integrate into any CI pipeline without vendor lock-in.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Bandit
&lt;/h2&gt;

&lt;p&gt;Bandit was originally developed by the OpenStack Security Project and is now maintained under the Python Code Quality Authority (PyCQA). It works by parsing Python source code into an &lt;strong&gt;Abstract Syntax Tree (AST)&lt;/strong&gt; and running a set of security-focused plugins against it — no code execution involved, which makes it fast and safe to run on any repository, including untrusted third-party code.&lt;/p&gt;

&lt;p&gt;Key features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;100% open source, no account or license required.&lt;/li&gt;
&lt;li&gt;Detects common issues: use of &lt;code&gt;eval()&lt;/code&gt;/&lt;code&gt;exec()&lt;/code&gt;, hardcoded passwords, insecure &lt;code&gt;subprocess&lt;/code&gt; calls, weak hashing algorithms (MD5/SHA1 for security purposes), unsafe YAML loading, SQL string concatenation, and more.&lt;/li&gt;
&lt;li&gt;Configurable severity/confidence thresholds, so teams can fail builds only on high-severity findings.&lt;/li&gt;
&lt;li&gt;Native CI/CD integration via exit codes, plus JSON/HTML/CSV report output.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Installing and Running Bandit
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;bandit

&lt;span class="c"&gt;# Scan a single file&lt;/span&gt;
bandit example.py

&lt;span class="c"&gt;# Scan an entire project recursively&lt;/span&gt;
bandit &lt;span class="nt"&gt;-r&lt;/span&gt; ./src

&lt;span class="c"&gt;# Generate a JSON report for CI pipelines&lt;/span&gt;
bandit &lt;span class="nt"&gt;-r&lt;/span&gt; ./src &lt;span class="nt"&gt;-f&lt;/span&gt; json &lt;span class="nt"&gt;-o&lt;/span&gt; bandit_report.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  A Real Example
&lt;/h2&gt;

&lt;p&gt;Consider this deliberately vulnerable Python snippet — the kind of code that often slips into real projects unnoticed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;yaml&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;hash_password&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Weak hashing algorithm for security purposes
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;md5&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;hexdigest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run_backup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Command injection risk: shell=True with unsanitized input
&lt;/span&gt;    &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tar -cvf backup.tar &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shell&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;load_config&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# Unsafe deserialization
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;yaml&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running &lt;code&gt;bandit -r .&lt;/code&gt; against this file produces output like: &lt;br&gt;
Each finding includes a rule ID (&lt;code&gt;B303&lt;/code&gt;, &lt;code&gt;B602&lt;/code&gt;, &lt;code&gt;B506&lt;/code&gt;), a severity/confidence rating, and the exact file and line number — enough context for a developer to fix the issue without needing a security background.&lt;/p&gt;
&lt;h2&gt;
  
  
  Fixing the Findings
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;yaml&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;hash_password&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Use a strong, purpose-built hashing function instead of MD5
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sha256&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;hexdigest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run_backup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Avoid shell=True; pass arguments as a list instead
&lt;/span&gt;    &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tar&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-cvf&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;backup.tar&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;load_config&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# safe_load restricts deserialization to simple Python objects
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;yaml&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;safe_load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Re-running Bandit against the fixed file returns zero findings — confirming the issues were resolved.&lt;/p&gt;
&lt;h2&gt;
  
  
  Integrating Bandit into a CI Pipeline
&lt;/h2&gt;

&lt;p&gt;A minimal GitHub Actions workflow that fails the build on any high-severity finding:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;SAST Scan with Bandit&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;bandit-scan&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-python@v5&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;python-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3.11"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install Bandit&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pip install bandit&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run Bandit&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bandit -r ./src -ll&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;-ll&lt;/code&gt; flag tells Bandit to only report (and fail on) &lt;strong&gt;medium and high severity&lt;/strong&gt; issues, filtering out noise from low-severity findings during early adoption.&lt;/p&gt;

&lt;h2&gt;
  
  
  Public Repository Example
&lt;/h2&gt;

&lt;p&gt;You can see Bandit applied to a real, public codebase here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/PyCQA/bandit" rel="noopener noreferrer"&gt;github.com/PyCQA/bandit&lt;/a&gt;&lt;/strong&gt; — the tool's own repository, which also runs Bandit against itself as part of its CI pipeline.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/DefectDojo/django-DefectDojo" rel="noopener noreferrer"&gt;github.com/OWASP/django-DefectDojo&lt;/a&gt;&lt;/strong&gt; — a large, real-world Django application (a vulnerability management platform, ironically) where Bandit-style static analysis patterns are commonly applied and can be run directly against the source.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Cloning either repo and running &lt;code&gt;bandit -r .&lt;/code&gt; locally shows the tool working against production-scale code, not just toy examples.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Bandit Fits Among SAST Tools
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Language scope&lt;/th&gt;
&lt;th&gt;License&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Bandit&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Python only&lt;/td&gt;
&lt;td&gt;Open source (Apache 2.0)&lt;/td&gt;
&lt;td&gt;Lightweight, AST-based, fast&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Brakeman&lt;/td&gt;
&lt;td&gt;Ruby on Rails only&lt;/td&gt;
&lt;td&gt;Open source&lt;/td&gt;
&lt;td&gt;Rails-specific vulnerability patterns&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SpotBugs + FindSecBugs&lt;/td&gt;
&lt;td&gt;Java&lt;/td&gt;
&lt;td&gt;Open source&lt;/td&gt;
&lt;td&gt;Bytecode analysis, plugin-based&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CodeQL&lt;/td&gt;
&lt;td&gt;Multi-language&lt;/td&gt;
&lt;td&gt;Free for public repos&lt;/td&gt;
&lt;td&gt;Query-based, very powerful but heavier setup&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PMD&lt;/td&gt;
&lt;td&gt;Java, Apex, others&lt;/td&gt;
&lt;td&gt;Open source&lt;/td&gt;
&lt;td&gt;General code quality + some security rules&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Compared to multi-language commercial platforms, Bandit trades breadth for simplicity: it does one language well, has no licensing cost, and can be added to a pipeline in minutes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;SAST doesn't require an expensive enterprise platform to start delivering value. A focused, open-source tool like Bandit can catch real, exploitable issues — insecure hashing, command injection, unsafe deserialization — directly in the CI pipeline, before code is merged. For teams working primarily in Python, it's a practical, zero-cost entry point into DevSecOps practices, and a good complement to broader multi-language tools as security maturity grows.&lt;/p&gt;

</description>
      <category>security</category>
      <category>python</category>
      <category>sast</category>
      <category>devsecops</category>
    </item>
    <item>
      <title>Applying API Testing Frameworks — A Hands-On Example with REST Assured</title>
      <dc:creator>Mauricio Choqueña Choque</dc:creator>
      <pubDate>Sun, 05 Jul 2026 19:45:40 +0000</pubDate>
      <link>https://dev.to/mauchoquenachoque/applying-api-testing-frameworks-a-hands-on-example-with-rest-assured-3fn</link>
      <guid>https://dev.to/mauchoquenachoque/applying-api-testing-frameworks-a-hands-on-example-with-rest-assured-3fn</guid>
      <description>&lt;h2&gt;
  
  
  Why API Testing Frameworks Matter
&lt;/h2&gt;

&lt;p&gt;APIs are the backbone of modern software — they connect frontends, mobile apps, microservices, and third-party integrations. If an API breaks silently, everything built on top of it breaks too. That's why API testing frameworks exist: to validate that endpoints return the right status codes, the right data structure, and the right values, automatically, on every code change.&lt;/p&gt;

&lt;p&gt;There are dozens of tools in this space — Postman, SoapUI, Karate DSL, JMeter, Swagger, Katalon Studio — each with different trade-offs between ease of use, coding requirements, and CI/CD integration. This article focuses on one of the most widely adopted &lt;strong&gt;code-based&lt;/strong&gt; frameworks: &lt;strong&gt;REST Assured&lt;/strong&gt;, a Java DSL built specifically to make testing REST APIs readable and maintainable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why REST Assured
&lt;/h2&gt;

&lt;p&gt;Unlike GUI-based tools (Postman, SoapUI), REST Assured is a &lt;strong&gt;library&lt;/strong&gt;, not an application — tests are written in Java and run like any other unit test (JUnit/TestNG), which means they plug directly into existing CI pipelines (Jenkins, GitHub Actions, GitLab CI) without any extra setup.&lt;/p&gt;

&lt;p&gt;Key features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;BDD-style syntax (&lt;code&gt;given() / when() / then()&lt;/code&gt;), close to plain English.&lt;/li&gt;
&lt;li&gt;Built-in JSON/XML response validation — no need to manually parse responses.&lt;/li&gt;
&lt;li&gt;Works seamlessly alongside Selenium/Serenity for combined UI + API test suites.&lt;/li&gt;
&lt;li&gt;No need to be an HTTP protocol expert to write solid tests.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A Real Example
&lt;/h2&gt;

&lt;p&gt;Here's a real, working REST Assured test against a public test API (&lt;a href="https://reqres.in" rel="noopener noreferrer"&gt;reqres.in&lt;/a&gt;, commonly used for practicing API testing):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;io.restassured.RestAssured&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;io.restassured.response.Response&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.junit.jupiter.api.Test&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;static&lt;/span&gt; &lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;restassured&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;RestAssured&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;given&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;static&lt;/span&gt; &lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;hamcrest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Matchers&lt;/span&gt;&lt;span class="o"&gt;.*;&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserApiTest&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@Test&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;testGetSingleUser&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;given&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;baseUri&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://reqres.in/api"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;when&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/users/2"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;then&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;statusCode&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"data.id"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;equalTo&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"data.email"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;containsString&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"@reqres.in"&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"data.first_name"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;notNullValue&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@Test&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;testCreateUser&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;requestBody&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"{ \"name\": \"morpheus\", \"job\": \"leader\" }"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

        &lt;span class="nc"&gt;Response&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
            &lt;span class="n"&gt;given&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;baseUri&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://reqres.in/api"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;contentType&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"application/json"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;requestBody&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;when&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;post&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/users"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;then&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;statusCode&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;201&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;equalTo&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"morpheus"&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"job"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;equalTo&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"leader"&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;extract&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;response&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

        &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Created user ID: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;jsonPath&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;getString&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"id"&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What this example shows in practice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;given()&lt;/code&gt;&lt;/strong&gt; sets up the request (base URL, headers, body).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;when()&lt;/code&gt;&lt;/strong&gt; performs the action (&lt;code&gt;GET&lt;/code&gt;, &lt;code&gt;POST&lt;/code&gt;, etc.).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;then()&lt;/code&gt;&lt;/strong&gt; validates the response — status code, JSON fields, string patterns — all in a single fluent chain.&lt;/li&gt;
&lt;li&gt;No manual JSON parsing, no raw HTTP client boilerplate: the assertions read almost like a specification of what the API should do.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Public Repository Example
&lt;/h2&gt;

&lt;p&gt;You can see this exact pattern in real open-source repositories that use REST Assured against public test APIs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/rest-assured/rest-assured" rel="noopener noreferrer"&gt;github.com/rest-assured/rest-assured&lt;/a&gt;&lt;/strong&gt; — the official framework repository, including its own extensive test suite as a live reference.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/vsingh181/reqres-api-automation" rel="noopener noreferrer"&gt;github.com/vsingh181/reqres-api-automation&lt;/a&gt;&lt;/strong&gt; — a public example project testing the &lt;code&gt;reqres.in&lt;/code&gt; API specifically, similar in structure to the snippet above.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both repositories can be cloned and run locally with Maven or Gradle to see the tests execute against a live API.&lt;/p&gt;

&lt;h2&gt;
  
  
  Comparing to Other Frameworks
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Framework&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Coding required&lt;/th&gt;
&lt;th&gt;Best for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;REST Assured&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Java library&lt;/td&gt;
&lt;td&gt;Yes (Java)&lt;/td&gt;
&lt;td&gt;Teams already using JUnit/TestNG, CI-first workflows&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Postman/Newman&lt;/td&gt;
&lt;td&gt;GUI + CLI runner&lt;/td&gt;
&lt;td&gt;Minimal&lt;/td&gt;
&lt;td&gt;Quick manual testing, exploratory testing, non-devs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Karate DSL&lt;/td&gt;
&lt;td&gt;Gherkin-based DSL&lt;/td&gt;
&lt;td&gt;Minimal&lt;/td&gt;
&lt;td&gt;BDD scenarios without full Java knowledge&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;JMeter&lt;/td&gt;
&lt;td&gt;GUI + scripting&lt;/td&gt;
&lt;td&gt;Minimal&lt;/td&gt;
&lt;td&gt;Load testing repurposed for functional checks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SoapUI&lt;/td&gt;
&lt;td&gt;GUI&lt;/td&gt;
&lt;td&gt;Minimal (Pro: Groovy)&lt;/td&gt;
&lt;td&gt;SOAP-heavy legacy systems&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;REST Assured sits closer to the "developer-first" end of the spectrum — it requires knowing Java, but in exchange gives full programmatic control: loops, conditionals, data-driven tests from CSV/JSON files, and direct integration into the same test suite as unit tests.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Choosing an API testing framework is less about which tool is "best" overall, and more about matching the tool to the team. For a team of developers already comfortable with Java and JUnit, REST Assured turns API contracts into readable, maintainable, CI-friendly test code — with no separate GUI tool to install or maintain. For teams with mixed technical backgrounds, a GUI-first tool like Postman may still be the faster path to adoption.&lt;/p&gt;

&lt;p&gt;The best way to evaluate any API testing framework, as always, is to clone a real repository — like the ones linked above — and run the suite yourself.&lt;/p&gt;

</description>
      <category>api</category>
      <category>testing</category>
      <category>java</category>
      <category>automation</category>
    </item>
    <item>
      <title>CI Testing Management Tools Compared — A Hands-On Look at GitHub Actions</title>
      <dc:creator>Mauricio Choqueña Choque</dc:creator>
      <pubDate>Sun, 05 Jul 2026 17:59:07 +0000</pubDate>
      <link>https://dev.to/mauchoquenachoque/ci-testing-management-tools-compared-a-hands-on-look-at-github-actions-3ck4</link>
      <guid>https://dev.to/mauchoquenachoque/ci-testing-management-tools-compared-a-hands-on-look-at-github-actions-3ck4</guid>
      <description>&lt;h2&gt;
  
  
  Why "Testing Management Tools" Matter
&lt;/h2&gt;

&lt;p&gt;Every serious software team eventually asks the same question: how do we make sure tests run automatically, consistently, and block bad code from reaching production? The answer is a CI/CD pipeline tool — GitHub Actions, GitLab CI, Jenkins, CircleCI, TeamCity, Travis CI, Bitbucket Pipelines, Tekton, Harness, and others all solve this problem, but with different philosophies, pricing models, and levels of ceremony.&lt;/p&gt;

&lt;p&gt;This article focuses on &lt;strong&gt;GitHub Actions&lt;/strong&gt;, using a real, public repository to show exactly how automated test management looks in practice — not just in theory.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where GitHub Actions Fits in the Landscape
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Hosting&lt;/th&gt;
&lt;th&gt;Config format&lt;/th&gt;
&lt;th&gt;Best for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;GitHub Actions&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Cloud (GitHub-hosted or self-hosted runners)&lt;/td&gt;
&lt;td&gt;YAML in &lt;code&gt;.github/workflows/&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Projects already on GitHub, fastest setup&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GitLab CI/CD&lt;/td&gt;
&lt;td&gt;Cloud or self-hosted&lt;/td&gt;
&lt;td&gt;YAML (&lt;code&gt;.gitlab-ci.yml&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;Teams fully inside GitLab, built-in Docker registry&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Jenkins&lt;/td&gt;
&lt;td&gt;Self-hosted&lt;/td&gt;
&lt;td&gt;Groovy (&lt;code&gt;Jenkinsfile&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;Full control, complex enterprise pipelines&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CircleCI&lt;/td&gt;
&lt;td&gt;Cloud&lt;/td&gt;
&lt;td&gt;YAML (&lt;code&gt;.circleci/config.yml&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;Fast parallel builds, strong caching&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TeamCity&lt;/td&gt;
&lt;td&gt;Self-hosted or cloud&lt;/td&gt;
&lt;td&gt;Kotlin DSL or UI-based&lt;/td&gt;
&lt;td&gt;Large enterprise, strong Windows/.NET support&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Travis CI&lt;/td&gt;
&lt;td&gt;Cloud&lt;/td&gt;
&lt;td&gt;YAML (&lt;code&gt;.travis.yml&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;Legacy open-source projects&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bitbucket Pipelines&lt;/td&gt;
&lt;td&gt;Cloud&lt;/td&gt;
&lt;td&gt;YAML (&lt;code&gt;bitbucket-pipelines.yml&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;Teams using Bitbucket + Jira&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tekton&lt;/td&gt;
&lt;td&gt;Kubernetes-native&lt;/td&gt;
&lt;td&gt;YAML CRDs&lt;/td&gt;
&lt;td&gt;Cloud-native, container-first pipelines&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Harness&lt;/td&gt;
&lt;td&gt;Cloud/self-hosted&lt;/td&gt;
&lt;td&gt;YAML + UI&lt;/td&gt;
&lt;td&gt;CD-focused, feature flags, progressive delivery&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;GitHub Actions is a good starting point for this comparison because it requires &lt;strong&gt;zero external infrastructure&lt;/strong&gt; — the pipeline lives in the same repository as the code, and any public GitHub repo can be used as a live example.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Real Example: Automated Testing Workflow
&lt;/h2&gt;

&lt;p&gt;Here is a realistic workflow file that runs a Python test suite on every push and pull request — the kind of file you'd find in thousands of public repositories under &lt;code&gt;.github/workflows/tests.yml&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;\&lt;/code&gt;`yaml&lt;br&gt;
name: Run Tests&lt;/p&gt;

&lt;p&gt;on:&lt;br&gt;
  push:&lt;br&gt;
    branches: [ main ]&lt;br&gt;
  pull_request:&lt;br&gt;
    branches: [ main ]&lt;/p&gt;

&lt;p&gt;jobs:&lt;br&gt;
  test:&lt;br&gt;
    runs-on: ubuntu-latest&lt;br&gt;
    strategy:&lt;br&gt;
      matrix:&lt;br&gt;
        python-version: ["3.9", "3.10", "3.11"]&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;steps:
  - name: Checkout repository
    uses: actions/checkout@v4

  - name: Set up Python ${{ matrix.python-version }}
    uses: actions/setup-python@v5
    with:
      python-version: ${{ matrix.python-version }}

  - name: Install dependencies
    run: |
      python -m pip install --upgrade pip
      pip install -r requirements.txt
      pip install pytest pytest-cov

  - name: Run tests with coverage
    run: pytest --cov=src --cov-report=xml

  - name: Upload coverage report
    uses: actions/upload-artifact@v4
    with:
      name: coverage-report
      path: coverage.xml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;`&lt;code&gt;\&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;A few things worth pointing out for anyone comparing tools:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Matrix strategy&lt;/strong&gt;: this single job actually runs &lt;strong&gt;three times&lt;/strong&gt; (Python 3.9, 3.10, 3.11), testing compatibility across versions without writing three separate jobs. CircleCI and GitLab CI support similar matrix features, but Jenkins requires more manual scripting to achieve the same result.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reusable actions&lt;/strong&gt; (&lt;code&gt;actions/checkout&lt;/code&gt;, &lt;code&gt;actions/setup-python&lt;/code&gt;): GitHub Actions has a large marketplace of pre-built steps, which reduces boilerplate compared to Jenkins' plugin-heavy model.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Artifacts&lt;/strong&gt;: the coverage report is uploaded as a build artifact, viewable directly from the GitHub Actions UI — no need for a separate reporting server.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Public Repository Example
&lt;/h2&gt;

&lt;p&gt;You can see this exact pattern live in real open-source projects. For example, the workflow structure above is modeled directly on the CI setup used by:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/pallets/flask" rel="noopener noreferrer"&gt;github.com/pallets/flask&lt;/a&gt;&lt;/strong&gt; — check &lt;code&gt;.github/workflows/tests.yml&lt;/code&gt; for a production example of matrix-based testing across Python versions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/psf/requests" rel="noopener noreferrer"&gt;github.com/psf/requests&lt;/a&gt;&lt;/strong&gt; — another widely used repo with a clean, minimal GitHub Actions test pipeline.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both are public and can be cloned or forked to study or reuse the exact YAML configuration.&lt;/p&gt;

&lt;h2&gt;
  
  
  What This Reveals About "Testing Management" as a Category
&lt;/h2&gt;

&lt;p&gt;Comparing these tools side by side shows that the differences aren't really about &lt;em&gt;whether&lt;/em&gt; tests can be automated — nearly all of them can run &lt;code&gt;pytest&lt;/code&gt;, &lt;code&gt;jest&lt;/code&gt;, &lt;code&gt;mvn test&lt;/code&gt;, etc. The differences show up in:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Where configuration lives&lt;/strong&gt; (YAML in-repo vs. Groovy scripts vs. UI-based config in TeamCity/Harness).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Native integrations&lt;/strong&gt; (GitHub Actions ties tightly to GitHub PRs/checks; Bitbucket Pipelines ties to Jira; Tekton ties to Kubernetes).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Infrastructure burden&lt;/strong&gt; (Jenkins and TeamCity need a server you maintain; GitHub Actions, CircleCI, and Travis CI are fully managed).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scaling and parallelism&lt;/strong&gt; (CircleCI and Tekton are built with heavy parallel/container workloads in mind).&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;For a team already hosting code on GitHub, GitHub Actions offers the lowest-friction path to automated testing: no servers to maintain, a large marketplace of reusable steps, and matrix builds that make cross-version testing trivial. Other tools in this comparison — Jenkins, TeamCity, Harness — trade that simplicity for more control, which matters more in large enterprise environments with complex, multi-stage deployment pipelines.&lt;/p&gt;

&lt;p&gt;The best way to evaluate any of these tools isn't reading documentation — it's cloning a real public repository, like &lt;code&gt;flask&lt;/code&gt; or &lt;code&gt;requests&lt;/code&gt; above, and watching the pipeline run.&lt;/p&gt;

</description>
      <category>cicd</category>
      <category>testing</category>
      <category>githubactions</category>
      <category>devops</category>
    </item>
    <item>
      <title>Agentes que se auto-corrigen: Text-to-SQL con smolagents (Hugging Face)</title>
      <dc:creator>Mauricio Choqueña Choque</dc:creator>
      <pubDate>Sun, 05 Jul 2026 17:51:15 +0000</pubDate>
      <link>https://dev.to/mauchoquenachoque/agentes-que-se-auto-corrigen-text-to-sql-con-smolagents-hugging-face-4dig</link>
      <guid>https://dev.to/mauchoquenachoque/agentes-que-se-auto-corrigen-text-to-sql-con-smolagents-hugging-face-4dig</guid>
      <description>&lt;h2&gt;
  
  
  El problema de los pipelines "texto → SQL" tradicionales
&lt;/h2&gt;

&lt;p&gt;La mayoría de las soluciones de IA para bases de datos siguen el mismo patrón: el usuario escribe una pregunta en lenguaje natural, un modelo la traduce a una consulta SQL, y esa consulta se ejecuta directamente contra la base de datos. Es simple, pero frágil: si el modelo genera una consulta incorrecta, esta puede &lt;strong&gt;ejecutarse sin errores&lt;/strong&gt; y devolver un resultado que parece válido pero no lo es. Nadie se entera de que la respuesta está mal, porque no hubo ningún fallo visible que lo delate.&lt;/p&gt;

&lt;p&gt;La documentación oficial de &lt;strong&gt;smolagents&lt;/strong&gt;, el framework de agentes de Hugging Face, propone una alternativa: en vez de un pipeline de un solo paso, construir un &lt;strong&gt;agente&lt;/strong&gt; capaz de revisar el resultado de su propia consulta y decidir si necesita corregirla. Este patrón —conocido como &lt;strong&gt;ReAct&lt;/strong&gt; (Reasoning + Acting)— convierte una traducción ciega en un proceso iterativo de prueba y verificación.&lt;/p&gt;

&lt;h2&gt;
  
  
  El stack utilizado
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;smolagents&lt;/strong&gt; — framework de agentes de código de Hugging Face.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;CodeAgent&lt;/code&gt;&lt;/strong&gt; — la clase principal del framework: un agente que razona escribiendo y ejecutando código, iterando sobre sus propios resultados anteriores.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SQLAlchemy&lt;/strong&gt; — para crear una base de datos SQLite en memoria y ejecutar las consultas.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;InferenceClientModel&lt;/code&gt;&lt;/strong&gt; — conecta el agente con modelos alojados en la Inference API de Hugging Face (Serverless o dedicada).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Repositorio del framework:&lt;/strong&gt; &lt;a href="https://github.com/huggingface/smolagents" rel="noopener noreferrer"&gt;github.com/huggingface/smolagents&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Documentación del ejemplo:&lt;/strong&gt; &lt;a href="https://huggingface.co/docs/smolagents/examples/text_to_sql" rel="noopener noreferrer"&gt;Self-correcting Text-to-SQL&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Paso a paso: cómo se construye el agente
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Preparar la base de datos
&lt;/h3&gt;

&lt;p&gt;Se crea una tabla &lt;code&gt;receipts&lt;/code&gt; (recibos) en SQLite, con columnas &lt;code&gt;receipt_id&lt;/code&gt;, &lt;code&gt;customer_name&lt;/code&gt;, &lt;code&gt;price&lt;/code&gt; y &lt;code&gt;tip&lt;/code&gt;, y se insertan algunas filas de ejemplo (nombres de clientes, precios y propinas).&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Convertir la tabla en una herramienta (&lt;code&gt;tool&lt;/code&gt;) para el agente
&lt;/h3&gt;

&lt;p&gt;Aquí está el detalle más importante del diseño: la descripción de la tabla &lt;strong&gt;no se le "explica" al modelo en un prompt suelto&lt;/strong&gt;, sino que se incrusta directamente en el docstring de la función &lt;code&gt;sql_engine&lt;/code&gt;, decorada con &lt;code&gt;@tool&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;\&lt;/code&gt;`python&lt;br&gt;
@tool&lt;br&gt;
def sql_engine(query: str) -&amp;gt; str:&lt;br&gt;
    """&lt;br&gt;
    Allows you to perform SQL queries on the table. Returns a string representation of the result.&lt;br&gt;
    The table is named 'receipts'. Its description is as follows:&lt;br&gt;
        Columns:&lt;br&gt;
        - receipt_id: INTEGER&lt;br&gt;
        - customer_name: VARCHAR(16)&lt;br&gt;
        - price: FLOAT&lt;br&gt;
        - tip: FLOAT&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Args:
    query: The query to perform. This should be correct SQL.
"""
output = ""
with engine.connect() as con:
    rows = con.execute(text(query))
    for row in rows:
        output += "\\n" + str(row)
return output
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;`&lt;code&gt;\&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Ese docstring es lo que el &lt;code&gt;CodeAgent&lt;/code&gt; "lee" para entender qué esquema tiene disponible y cómo debe formar sus consultas.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Crear el agente
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;\&lt;/code&gt;`python&lt;br&gt;
from smolagents import CodeAgent, InferenceClientModel&lt;/p&gt;

&lt;p&gt;agent = CodeAgent(&lt;br&gt;
    tools=[sql_engine],&lt;br&gt;
    model=InferenceClientModel(model_id="meta-llama/Llama-3.1-8B-Instruct"),&lt;br&gt;
)&lt;br&gt;
agent.run("Can you give me the name of the client who got the most expensive receipt?")&lt;br&gt;
`&lt;code&gt;\&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Con solo esto, el agente ya es capaz de generar la consulta SQL correspondiente, ejecutarla mediante la herramienta &lt;code&gt;sql_engine&lt;/code&gt;, y devolver el nombre del cliente con el recibo más caro.&lt;/p&gt;

&lt;h2&gt;
  
  
  Nivel 2: cuando el esquema se complica (joins)
&lt;/h2&gt;

&lt;p&gt;La prueba de fuego llega cuando se agrega una &lt;strong&gt;segunda tabla&lt;/strong&gt;, &lt;code&gt;waiters&lt;/code&gt; (meseros), que relaciona cada &lt;code&gt;receipt_id&lt;/code&gt; con el nombre del mesero que lo atendió. Esto obliga al agente a razonar sobre un &lt;strong&gt;join&lt;/strong&gt; entre dos tablas para responder algo como "¿qué mesero recibió más propinas en total?".&lt;/p&gt;

&lt;p&gt;Como el esquema cambió, la descripción de la herramienta &lt;code&gt;sql_engine&lt;/code&gt; se &lt;strong&gt;actualiza dinámicamente&lt;/strong&gt;, incluyendo ahora las columnas de ambas tablas:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;\&lt;/code&gt;`python&lt;br&gt;
sql_engine.description = updated_description&lt;/p&gt;

&lt;p&gt;agent = CodeAgent(&lt;br&gt;
    tools=[sql_engine],&lt;br&gt;
    model=InferenceClientModel(model_id="Qwen/Qwen3-Next-80B-A3B-Thinking"),&lt;br&gt;
)&lt;br&gt;
agent.run("Which waiter got more total money from tips?")&lt;br&gt;
`&lt;code&gt;\&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Dos cosas clave suceden aquí:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;El agente actualiza su comprensión del entorno&lt;/strong&gt; simplemente porque la descripción de su herramienta cambió — no hace falta reentrenar nada ni reescribir la lógica del agente.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;El modelo se cambia por uno más potente&lt;/strong&gt; (Qwen3-Next-80B-A3B-Thinking en lugar de Llama-3.1-8B-Instruct) porque la tarea de razonar sobre un join es más difícil que una consulta simple sobre una sola tabla. La documentación confirma que este cambio de modelo mejora notablemente el resultado frente a consultas más complejas.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Por qué este enfoque es superior a un pipeline directo
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Pipeline directo texto→SQL&lt;/th&gt;
&lt;th&gt;Agente con smolagents (ReAct)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Genera y ejecuta la consulta en un solo paso&lt;/td&gt;
&lt;td&gt;Genera, ejecuta, &lt;strong&gt;observa el resultado&lt;/strong&gt; y puede corregir&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Un error puede pasar desapercibido&lt;/td&gt;
&lt;td&gt;El agente puede detectar resultados sospechosos o vacíos y reintentar&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;El esquema debe estar "hardcodeado" en el prompt&lt;/td&gt;
&lt;td&gt;El esquema vive en la descripción de la herramienta y se puede actualizar en caliente&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rígido ante consultas complejas (joins, subconsultas)&lt;/td&gt;
&lt;td&gt;Escala mejor cambiando de modelo o ajustando la herramienta&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Conclusión
&lt;/h2&gt;

&lt;p&gt;El ejemplo de smolagents demuestra que el salto de calidad en text-to-SQL no viene solo de tener un modelo de lenguaje más grande, sino de &lt;strong&gt;darle al modelo la capacidad de actuar, observar y corregir&lt;/strong&gt;, en lugar de generar una única respuesta "a ciegas". Esto es especialmente valioso en bases de datos reales, donde los esquemas cambian, las consultas se vuelven complejas (joins, agregaciones, filtros anidados), y un error silencioso puede llevar a decisiones basadas en datos incorrectos.&lt;/p&gt;

&lt;p&gt;Para quien quiera experimentar: el repositorio &lt;a href="https://github.com/huggingface/smolagents" rel="noopener noreferrer"&gt;huggingface/smolagents&lt;/a&gt; incluye este ejemplo listo para correr en Google Colab o SageMaker Studio Lab, y es un excelente punto de partida para adaptar el patrón a una base de datos propia.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>sql</category>
      <category>huggingface</category>
      <category>python</category>
    </item>
  </channel>
</rss>
