How I Run Builds and Tests in VSCode Efficiently

Article summary

Over the last couple of years, I’ve developed a simple workflow to run builds, tests, and other operations during development that allows me to easily and efficiently examine results. Rather than struggling to find the output in a wall of text in a terminal with limited keyboard support, I simply log each build and test run to a file. Adding a timestamp to each line helps me to avoid stale data.

Build

I simply open a terminal and run build, which is an executable script in my path. I keep a tab open for the build.log and can see the timestamped output appear in the editor tab as the build progresses.

Here’s my build script:

#!/usr/bin/env bash

yarn build $* 2>&1                        |  # Run build with params and redirect stderr to stdout
  ts "[%Y-%m-%d %H:%M:%S] "               |  # Prefix each line with a timestamp
  stdbuf -o0 sed -E "s/.\[[0-9]{0,2}m//g" |  # Flush buffer for each line and remove ANSI color codes
  tee ~/app/build.log                        # Log to file and write to console

In this example, I’m using a node build environment. You can easily adjust to your needs.

Run

I also run my apps the same way as with the build script. This is especially nice since the timestamps can help me to focus on the right timeline since many errors/debug output can appear over time.

Here’s my dev script:

#!/usr/bin/env bash

yarn dev $* 2>&1                          |  # Run dev server with params and redirect stderr to stdout
  ts "[%Y-%m-%d %H:%M:%S] "               |  # Prefix each line with a timestamp
  stdbuf -o0 sed -E "s/.\[[0-9]{0,2}m//g" |  # Flush buffer for each line and remove ANSI color codes
  tee ~/app/dev.log                          # Log to file and write to console

Test

For running tests, it gets a bit more interesting. I have two modes — one for running all the test modules in the suite and one for running a single module (file).

We can run all tests by running rt (run test):

When running all tests, I output to a log file called tests-all.log. I name it specifically so I don’t overwrite this file when running a single test module as I am narrowing down the test failures.

Here’s my rt script:

#!/usr/bin/env bash

# Environment variables
NODE_ENV=test
CLEANUP=0

if [[ -z "$1" ]]; then
  # No parameters specified: run all tests

  yarn jest 2>&1                            |  # Run test and redirect stderr to stdout
    ts "[%Y-%m-%d %H:%M:%S] "               |  # Prefix each line with a timestamp
    stdbuf -o0 sed -E "s/.\[[0-9]{0,2}m//g" |  # Flush buffer for each line and remove ANSI color codes
    tee ~/app/test-all.log                     # Log to file and write to console

else
  # Test file specified: run single file

  # Environment variables enable more test verbosity
  # when running a single test file:
  LOG_LEVEL=debug
  DEBUG=knex:query,knex:bindings

  jest $* 2>&1                              |   # Run test and redirect stderr to stdout
    ts "[%Y-%m-%d %H:%M:%S] "               |   # Prefix each line with a timestamp
    stdbuf -o0 sed -E "s/.\[[0-9]{0,2}m//g" |   # Flush buffer for each line and remove ANSI color codes
    tee ~/app/test.log                          # Log to file and write to console
fi

We can run a single test module (file) very easily by binding a key to tell VSCode to execute the script for the currently-selected tab. This outputs to another file (test.log) to avoid overwriting the full test run log.

Just add a task to tasks.json like the following:

{
  "version": "2.0.0",
  "tasks": [
    {
      "label": "Run Test",
      "type": "shell",
      "command": "cd ~/app && rt ${relativeFile}",
      "problemMatcher": [],
      "group": {
        "kind": "test",
        "isDefault": true
      },
      "presentation": {
        "echo": true,
        "reveal": "never",
        "focus": false,
        "panel": "shared",
        "showReuseMessage": true,
        "clear": false
      }
    }
  ]
}

The ${relativeFile} gets replaced with the relative path to the file/tab you currently have selected.

Then to run the script easily, you can find a key (like F5) to kick off the task automatically:

Just add a task to keybindings.json like the following:

{
  "key": "f5",
  "command": "workbench.action.tasks.test"
}

This is great because you don’t even have to have your terminal open to kick off a test of a single module. I just keep my test file on the left and the output log on the right.

I currently use VSCode for development, but this can apply to other environments as well.