LINUX.ORG.RU

Unit-тестирование bash скриптов

 


0

1

Всем привет! Кто-нибудь когда-нибудь проводил нечто подобное? Если да, поделитесь опытом. Говорю сразу, я от тестирования очень далёк, знаю, что существует shunit, но мне нужно написать пару тестов именно вручную. Как это сделать? С чего начать? Нужно ли бить скрипт на несколько файлов (юнитов) или воротить всё прямо в нём? Сам скрипт очень простой, интерес чисто академический. Суть: принимает на вход некоторые параметры и в зависимости от них выводит хэловорлд разными способами. Наткнулся в интернете на такую конструкцию:

#!/bin/bash
 set -e
 errors=0
 results=$($script_under_test $args<<ENDTSTDATA
 # inputs
 # go
 # here
 #
 ENDTSTDATA
 )
 [ "$?" -ne 0 ] || {
     echo "Test returned error code $?" 2>&1
     let errors+=1
     }

 echo "$results" | grep -q $expected1 || {
      echo "Test Failed.  Expected $expected1"
      let errors+=1
 }
 # and so on, et cetera, ad infinitum, ad nauseum
 [ "$errors" -gt 0 ] && {
      echo "There were $errors errors found"
      exit 1
 }
Что тут куда совать? Не в техразделе, потому что это просто такая игра. При чём тут linux скажу по секрету, потом, если захотите...

Перемещено mono из talks

★★★★★

Последнее исправление: mono (всего исправлений: 1)

При чём тут linux скажу по секрету

Говори, нам всем очень интересно.

drull ★☆☆☆
()

Напиши на баше скрипт, который будет вызывать тестируемый скрипт, пропускать ответ через grep или ещё какую дрянь и анализировать ответ.
Или что ты хотел спросить?

Stahl ★★☆
()

Не тестировать

Писать на bash'е что-то более 100 строк есть мучение и риск, а меньше 100 строк можно и без модульного тестирования отладить.

Camel ★★★★★
()
Ответ на: комментарий от Stahl

Тестирование всех аспектов

Напиши на баше скрипт, который будет вызывать тестируемый скрипт, пропускать ответ через grep или ещё какую дрянь и анализировать ответ.

А если сценарий не только выводит что-то в выхлоп, но ещё вызывает команды (mdadm, vgchange, mkfs...), пишет в файлы? Чтобы проверять вызов этих команд нужна поддержка каких-то заменителей, mock-объектов. Как это делать в bash'е? Мученье.

Camel ★★★★★
()
Ответ на: комментарий от Stahl

Это же юнит-тестирование, а оно предполагает тестирование именно отдельных частей, а не всего скрипта разом.

Pyzia ★★★★★
() автор топика
Последнее исправление: Pyzia (всего исправлений: 1)
Ответ на: комментарий от Pyzia

Похоронить

Есть скрипт на баше в 500 строк, предлагаете переписать его на...чём? Питон? Перл? Паскаль? Groovy?

Беда. Переписать я предлагаю на Ruby, но ещё нужно оценить затраты на переписывание.

Camel ★★★★★
()
Ответ на: комментарий от Stahl

Yo dawgНапиши на баше скрипт, который будет вызывать тестируемый скрипт, пропускать ответ через скрипт и анализировать ответ скриптом.

heilkitty ★★
()
Ответ на: комментарий от heilkitty

Ты забыл в конце написать «скрипт». А то ни разу не грека через реку.

Stahl ★★☆
()
Последнее исправление: Stahl (всего исправлений: 1)
Ответ на: комментарий от Pyzia

это же именно юнит-тестирование, а оно предполагает тестирование именно отдельных частей, а не всего скрипта разом

Если скрипт без подпрограмм, то он целиком и есть юнит. Вызывай его с разными параметрами и грепай на предмет нужного выхлопа, как и сказали. И проверяй сайд-эффекты, чтобы было ещё веселей. Если у тебя библиотека функций - в тестовом скрипте подгружай её и дёргай функции таким же макаром. Не пойму, что тебя смущает. Хочешь тестовый фреймворк что-ли написать? Ну тогда лучше забей.

Hjorn
()
Ответ на: комментарий от Hjorn

Неее, чёто тут не так. Он в процессе выполнения присваивает значения разным переменным, потом в зависимости от значения этих переменных выполняет разные действия, в т.ч. присваивает значения другим переменным и так далее, т.е. этот хэловорд не так прост, нужно тестировать именно кусками.

Pyzia ★★★★★
() автор топика
Ответ на: комментарий от Pyzia

Вот я ж и говорю, трахаться придется с сайд-эффектами. Оно тебе надо?

Hjorn
()

разделяй и властвуй

anonymous
()

Не вижу смысла в unit-тестах для bash-скриптов, так как в большинстве случаев отладка происходит в процессе написания скрипта. Единственное что стоит проверять - это зависимости скрипта от других программ/скриптов. Например, вот тут есть хороший пример функции require - http://linuxhub.ru/viewtopic.php?f=23&t=1911

Punk
()
Ответ на: комментарий от Pyzia

Он в процессе выполнения присваивает значения разным переменным, потом в зависимости от значения этих переменных выполняет разные действия, в т.ч. присваивает значения другим переменным

дай угадаю: ты не слышал про слово local в bash? Тогда ты ССЗБ и тебе никакое тестирование не поможет.

Глобальные переменные работают лишь если строк меньше 50. Если больше, то это Ад и Израиль.

Т.ч. используй локальные переменные внутри bash-функций, и тогда всё будет работать почти без отладки.

emulek
()
Ответ на: комментарий от ritsufag

Рекомендую писать на нормальном яп, а не клее для гнутых утилит.

в умелых руках и пинус — молоток.

Для утилиток в 500 строк сложно найти что-то лучше bash'а.

emulek
()
Ответ на: комментарий от emulek

дай угадаю: ты не слышал про слово local в bash?

От local в баше проку — ноль. Ф-ции возвращают только код исполнения, все остальное приходится писать в какие-нибудь переменные и не ограничивать их областью видимости ф-ции. Это Ад и Израиль, но другого баша у нас нет :(

iVS ★★★★★
()
Последнее исправление: iVS (всего исправлений: 2)

Проще переписать на какой-нибудь python, а там unittest уже из коробки есть.

Kilte ★★★★★
()
Ответ на: комментарий от iVS

Ф-ции возвращают только код исполнения, все остальное приходится писать в какие-нибудь переменные и не ограничивать их областью видимости ф-ции. Это Ад и Израиль, но другого баша у нас нет :(

это твоя проблема.

emulek
()
Ответ на: комментарий от emulek

Не только моя, раз во всяких руби и питонах ф-ции возвращают значения. В баше global state by design, а local — костыль.

iVS ★★★★★
()
Ответ на: комментарий от iVS

для программ ≤500 строк нет нужды возвращать изолированные переменные.

ты можешь использовать локальные переменные для передачи значений между функциями:

#!/bin/bash

X=123;

function foo()
{
	echo "foo() ::X=$X"
	local X=456
	echo "foo() foo::X=$X"
	bar 789
	echo "foo() foo::X=$X"
}

function bar()
{
	echo "bar() foo::X=$X"
	echo "bar() increment foo::X"
	(( X++ ))
	echo "bar() foo::X=$X"
	local X="$1"
	echo "bar() bar::X=$X"
	echo "bar() decrement bar::X"
	(( X-- ))
	echo "bar() bar::X=$X"
}


echo "::X=$X"
foo
echo "::X=$X"

тут три переменных X. функция bar() может вернуть значение через foo::X, которая не является глобальной.

emulek
()
Ответ на: комментарий от emulek

для программ ≤500 строк нет нужды возвращать изолированные переменные.

ты можешь использовать локальные переменные для передачи значений между функциями:

Собственно, все, что ты делаешь — передаешь один раз значение bar 789. Возможно, это дело вкуса, но я предпочту давать переменным осмысленные названия, особенно, когда это не просто значение, а результат предыдущих нетривиальных действий.

iVS ★★★★★
()
Ответ на: комментарий от iVS

лови PoC

#!/bin/bash

function factorial_r()
{
	if (( x<=1 )); then
		y=$x
		return
	fi
	(( y = x - 1 ))
	local x=$y
	factorial_r
	(( y *= x ))
	# echo "$y"
}

function factorial()
{
	local y
	local x=$1
	factorial_r
	(( y *= x ))
	echo "$x! = $y"
}

factorial 7

что тут сложного?

emulek
()
Ответ на: комментарий от iVS

Возможно, это дело вкуса, но я предпочту давать переменным осмысленные названия

я тоже так делаю. А в чём проблема?

emulek
()
Ответ на: комментарий от emulek

Конечно, писать одну ф-цию и говорить «что тут сложного» каждый может. Но возьми одну ф-цию, вызови из нее другую и считай результат в первую с помощью локальных переменных. Вот простой пример:

foo() {
  X=1
  bar
  echo "$X"
}
bar() {
  (( X+=1 ))
}
foo
# 2

iVS ★★★★★
()
Ответ на: комментарий от iVS

# 2

а ты сколько хотел? У тебя X глобальное, и получается 1+1=2. Сделай X локальным внутри bar, если хочешь, что-бы внутри foo не менялось. Причём можно X внутри foo тоже сделать локальным, что-бы не было сайд-эффекта с глобальным X.

Конечно bash это тебе не C++, тут namespace'ов нет, да и если ты сделал локальное X, то до другого X(перекрытого) уже не добраться. Т.е. не получится писать myspace::X, myclass::X, и даже ::X. Да и ладно, это не нужно в программах на 500 строк.

Главное, что есть полноценные изолированные контексты для функций, т.е. сайд-эффекты можно исключить. А если ты не осилил — ССЗБ.

emulek
()
Ответ на: комментарий от emulek

Ты забыл препарат принять! Как отпустит, почитай и попробуй понять, что я писал:

Но возьми одну ф-цию, вызови из нее другую и считай результат в первую с помощью локальных переменных.

iVS ★★★★★
()
Ответ на: комментарий от iVS

не тупи. Здесь Unit-тестирование bash скриптов (комментарий) я сделал именно то, что ты просишь:

возьми одну ф-цию, вызови из нее другую и считай результат в первую с помощью локальных переменных.

причём сделал я это рекурсивно, что-бы ты не сомневался, что переменные в баше по настоящему локальны.

У меня исходные данные передаются в x, а результат возвращается в y. Т.к. результат для рекурсии не нужен, то на всех итерациях одна локальная y. А вот x для каждой итерации своя. Для лучшего понимания вставь второй строкой

set -x

emulek
()
Ответ на: комментарий от emulek

Здесь Unit-тестирование bash скриптов (комментарий) я сделал именно то, что ты просишь

Переменная y у тебя объявляется только в функции factorial и доступна из всех вызываемых рекурсивно ф-ций. Т.е., вместо действительно рекурсии f(n)=n*f(n-1) у тебя глобальная переменная y, которую ты изменяешь в каждой из ф-ций. А ты подменяешь мне понятия, потому что область видимости по-настоящему локальной переменной — только внутри одной ф-ции, где переменная объявляется. В этом смысле у тебя y не локальна и служит костылем для передачи значения из одной ф-ции в другую.

iVS ★★★★★
()
Ответ на: комментарий от iVS

Переменная y у тебя объявляется только в функции factorial и доступна из всех вызываемых рекурсивно ф-ций. Т.е., вместо действительно рекурсии f(n)=n*f(n-1) у тебя глобальная переменная y, которую ты изменяешь в каждой из ф-ций

это не баг, а фича. y локальная в factorial, но действительно видимая из любой factorial_r. А вот переменная x как ты хочешь: в любой итерации своя.

А ты подменяешь мне понятия, потому что область видимости по-настоящему локальной переменной — только внутри одной ф-ции, где переменная объявляется.

это в твоём любимом язычке так? Ну не везде это так. Например в сишке вполне можно получать доступ к переменным вызывающей функции(но не наоборот). А у тебя очевидно какие-то ненужные костыли. И наверняка есть костыли для обхода этого костыля. «Замыкания» какие-нить, да?

И всё это лирика, на практике сайдэффектов не будет с локальными переменными в bash'е. Чем ты недоволен?

emulek
()
Ответ на: комментарий от Pyzia

Есть скрипт на баше в 500 строк, предлагаете переписать его на...чём? Питон? Перл? Паскаль? Groovy?

Да хоть на JavaScript. Любой нормальный скриптовой язык лучше чем Bash.

rupert ★★★★★
()
Ответ на: комментарий от Pyzia

bash я худо-бедно знаю, а вот Ruby ещё нужно учить.

За пару дней худо-бедно подучишь Ruby и начнёшь на нём писать. А потом в процессе использования выучишь его ещё лучше.

rupert ★★★★★
()
Ответ на: комментарий от Pyzia

Беда в том, что bash я худо-бедно знаю, а вот Ruby ещё нужно учить

пиши на python

anonymous
()
Ответ на: комментарий от emulek

И всё это лирика, на практике сайдэффектов не будет с локальными переменными в bash'е. Чем ты недоволен?

Да не, я просто убедился, что моё понимание подхода в bash'е верно: заводишь переменную, которая видна из вызываемой ф-ции тоже, и туда складываешь результат. Хоть я считаю, что возвращать значение из ф-ции было бы логичнее, что соотв. не «моему любимому язычку», а следует из мат. определения ф-ции. В bash было бы логичнее называть их не ф-циями, а процедурами, имхо. Но заморачиваться этим вопросом не принято для скриптов не более 500 строк. Спасибо за разъяснения.

iVS ★★★★★
()
Ответ на: комментарий от iVS

Да не, я просто убедился, что моё понимание подхода в bash'е верно: заводишь переменную, которая видна из вызываемой ф-ции тоже, и туда складываешь результат.

да, но не глобальную.

Хоть я считаю, что возвращать значение из ф-ции было бы логичнее, что соотв. не «моему любимому язычку», а следует из мат. определения ф-ции. В bash было бы логичнее называть их не ф-циями, а процедурами, имхо

да. Как и в системе, «функции» в bash возвращают код возврата, фактически bool. Т.е. OK/0 если всё хорошо, и FAIL/неноль если ошибка. Кроме как выделить несколько вариантов ошибок(e.g. EINVAL, ENOENT, EACCESS, etc) эти коды не для чего не нужны.

А выходные параметры надо передавать как-то по другому, через переменные/файлы.

emulek
()

Пришёл к такой конструкции:

func ()
{
if [ "$y" != "$x" ]
 then
 let "error_count += 1"
fi
}

var1=$y
var2="x"
func
#Здесь сравниваются значения переменных, которые получены после прохождения скрипта с значением, которое должно быть, но таких значений много и приходится много раз делать
var1=$y
var2="x"
func
И каждый раз с новыми значениями. Как бы мне это всё зациклить, чтобы избавиться от повторяющегося кода? Да, я дерево...

Pyzia ★★★★★
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.