sstephenson / bats

Bash Automated Testing System
MIT License
7.12k stars 519 forks source link

run more than one command inside run #168

Open bcv opened 8 years ago

bcv commented 8 years ago

Hi,

This might be a silly question but in my case I have a persistent variable in a function and so want to call this function twice

run (func1 arg1 ; func2 arg2 )

doesnt seem to be welcomed by BATS

ZoomyCat commented 8 years ago

Creating a front end script for the function that runs both functions would solve this.

ztombol commented 8 years ago

@bcv Please use markdown's formatting abilities, especially code blocks, to improve readability of your posts.


Functions depending on side-effects

If all you want to do is test a function that relies on side-effects of others, simply call the functions causing the side-effects first and then run the command you want to test.

#!/usr/bin/env bats

increment_var() {
  (( ++VAR ))
}

print_var() {
  echo "$VAR"
}

@test 'Testing function that depends on side-effects' {
  increment_var   # VAR = 1
  increment_var   # VAR = 2
  run print_var
  [ "$output" == '2' ]
}

run uses command substitution to execute its arguments in a subshell. Variables set before the subshell, i.e. before run, are visible inside the subshell, i.e. to the command passed to run. On the other hand, any variable set inside a subshell will not persist when it returns.

Passing more than one command to run

If you really need to run more than one command, you can either wrap them in a function (or script as @edge226 suggested), or use bash -c.

Wrapper functions

Wrapping your functions in an extra function is a simple way of run-ing multiple functions at once.

#!/usr/bin/env bats

func_1() { echo '1'; }
func_2() { echo '2'; }

wrapper() {
  func_1
  func_2
}

@test "Passing more than one commands to \`run' with a wrapper function" {
  run wrapper
  [ "$status" -eq 0 ]        # return value of last function, i.e. `func_2'
  [ "$output" == $'1\n2' ]   # output of all functions
}

bash -c

Another solution is to run the commands in a new shell using bash -c. As far as I know this was first proposed by @inthecloud247 to solve the problem of run-ing commands that needs to pipe.

The disadvantage, or advantage depending on what you want, is that the commands are run in a new process and are thus isolated from the environment of the test case.

#!/usr/bin/env bats

@test "Passing more than one commands to \`run' with a wrapper function" {
  run bash -c 'echo 1; echo 2'
  [ "$status" -eq 0 ]        # return value of last command, i.e. `echo 2'
  [ "$output" == $'1\n2' ]   # output of all commands
}

Use the source

I encourage everybody to look into Bats' source when something is not clear. Yes, I know. User experience sucks if you need to read the source in order to use the program. Normally this is true, however a Bats test file is effectively a Bash script, which makes it very powerful. Bash is a simple scripting language with simple scoping rules and many quirks. Knowing a tiny bit about the insides of Bats is unavoidable to unlock its full potential.

With only about 900 lines Bats is very simple and lean, which I consider its biggest strength. You literally don't need to understand everything. For example printing the stack trace is not something you need to care about. Just having a rough overview of how Bats and some user facing functions, e.g. run and load, work is enough. The wiki has some helpful information on this too.