สคริปต์ทุบตีการทดสอบหน่วย


112

เรามีระบบที่มีสคริปต์ bash ที่ทำงานนอกเหนือจากโค้ด Java เนื่องจากเรากำลังพยายามทดสอบทุกสิ่งที่อาจพังได้และสคริปต์ทุบตีเหล่านั้นอาจพังเราจึงต้องการทดสอบ

ปัญหาคือยากที่จะทดสอบสคริปต์ทุบตี

มีวิธีหรือแนวทางปฏิบัติที่ดีที่สุดในการทดสอบ bash script หรือไม่? หรือเราควรเลิกใช้สคริปต์ทุบตีและมองหาทางเลือกอื่นที่สามารถทดสอบได้?


ดูเพิ่มเติม: stackoverflow.com/questions/1315624/…
Chen Levy


ภาพรวมของเครื่องมือที่มีอยู่: medium.com/wemake-services/…
sobolevn

คำตอบ:


48

จริงๆแล้วมีshunit2ซึ่งเป็นเฟรมเวิร์กการทดสอบหน่วยที่ใช้ xUnit สำหรับเชลล์สคริปต์ที่ใช้ Bourne ฉันไม่ได้ใช้มันเอง แต่มันก็คุ้มค่าที่จะลองดู

มีการถามคำถามที่คล้ายกันมาก่อน:


3
ฉันสามารถยืนยัน (เล่นสำนวนเจตนา) ได้ว่า shunit2 (เวอร์ชัน 2.1.6) เสียไปเล็กน้อยจนถึงปัจจุบัน assertNull และ assertNotNull ไม่ทำงานแม้ว่าคุณจะป้อนค่าโดยตรงก็ตาม assertEquals ทำงานได้ดี แต่ฉันคิดว่าตอนนี้ฉันจะต้องหมุนตัวเอง
เขาวงกต

@labyrinth คุณแน่ใจหรือไม่ว่าปัญหานี้ไม่ใช่กรณีนี้: github.com/esome/shunit2/issues/53 "ใช้ assertNull อย่างไรให้ถูกต้อง"
Victor Sergienko

1
@Victor เป็นไปได้แน่นอนว่าฉันไม่ระมัดระวังพอกับเครื่องหมายคำพูดคู่ของฉัน เร็ว ๆ นี้ฉันจะกลับเข้าสู่บทบาทที่ shunit2 หรือระบบทดสอบหน่วยทุบตีบางอย่างจะมีประโยชน์มาก ฉันจะลองอีกครั้ง
เขาวงกต

5
ฉันเป็นผู้ใช้และบางครั้งก็มีส่วนร่วมให้กับ shunit2 และฉันสามารถยืนยันได้ว่าโครงการยังคงมีชีวิตอยู่และดีในปี 2019
Alex Harvey

31

ฉันได้รับคำตอบต่อไปนี้จากกลุ่มสนทนา:

เป็นไปได้ที่จะอิมพอร์ต (รวมอะไรก็ได้) โพรซีเดอร์ (ฟังก์ชันอะไรก็ตามที่ตั้งชื่อ) จากไฟล์ภายนอก นั่นคือกุญแจสำคัญในการเขียนสคริปต์การทดสอบ: คุณแยกสคริปต์ของคุณออกเป็นโพรซีเดอร์อิสระที่สามารถนำเข้าสู่ทั้งสคริปต์ที่รันและสคริปต์ทดสอบของคุณจากนั้นคุณจะทำให้สคริปต์ที่รันอยู่นั้นง่ายที่สุด

วิธีนี้เหมือนกับการฉีดขึ้นต่อกันสำหรับสคริปต์และฟังดูสมเหตุสมผล ควรหลีกเลี่ยงการใช้สคริปต์ทุบตีและใช้ภาษาที่ทดสอบได้และคลุมเครือน้อยกว่า


4
ฉันไม่แน่ใจว่าควรโหวตขึ้นหรือลงในแง่หนึ่งการแบ่งเป็นส่วนเล็ก ๆ นั้นดี แต่ในมือสองฉันต้องการเฟรมเวิร์กไม่ใช่ชุดสคริปต์ที่กำหนดเอง
mpapis

10
แม้ว่าจะไม่มีอะไรผิดปกติกับ bash (ฉันเขียนหลายสคริปต์หลายสคริปต์) มันเป็นภาษาที่ยากที่จะเชี่ยวชาญ หลักการทั่วไปของฉันคือถ้าสคริปต์มีขนาดใหญ่พอที่จะต้องทำการทดสอบคุณควรเปลี่ยนไปใช้ภาษาสคริปต์ที่ทดสอบได้ง่าย
Doug

1
แต่บางครั้งคุณจำเป็นต้องมีบางอย่างที่สามารถหาได้จากเชลล์ของผู้ใช้ ไม่ชัดเจนสำหรับฉันว่าคุณจะทำอย่างไรโดยไม่ต้องใช้เชลล์สคริปต์
Itkovian

@Itkovian - ตัวอย่างเช่นคุณสามารถใช้ npm เพื่อส่งออกไฟล์ปฏิบัติการไปยังพา ธ ได้ดังนั้นจึงไม่จำเป็นต้องมีการจัดหา (แพ็คเกจ npm ของคุณจะต้องติดตั้งทั่วโลก)
Eliran Malka

1
ฉันจะทำตามคำแนะนำเกี่ยวกับการไม่ใช้ทุบตี :)
Maciej Wawrzyńczuk

30

TAP -compliant Bash testing: Bash Automated Testing System

TAP คือ Test Anything Protocol เป็นอินเทอร์เฟซแบบข้อความที่เรียบง่ายระหว่างโมดูลการทดสอบในสายรัดทดสอบ TAP เริ่มต้นชีวิตโดยเป็นส่วนหนึ่งของสายรัดทดสอบสำหรับ Perl แต่ตอนนี้มีการใช้งานใน C, C ++, Python, PHP, Perl, Java, JavaScript และอื่น ๆ


14
มันคุ้มค่าที่จะเปิดเผยว่า TAP คืออะไรและทำไมจึงควรใส่ใจไม่เช่นนั้นก็เป็นเพียงการคัดลอกวางที่ไร้ความหมาย
om-nom-nom

@ om-nom-nom: ตอนนี้ฉันเชื่อมโยงกับเว็บไซต์ TAP แล้ว
Janus Troelsen

7
เนื่องจากไม่มีใครพูดถึงสิ่งที่ไม่สามารถทำได้: TAP = Test Anything Protocol
JW

9

Nikita Sobolev เขียนบล็อกโพสต์ที่ยอดเยี่ยมโดยเปรียบเทียบกรอบการทดสอบ bash ที่แตกต่างกัน: การทดสอบแอปพลิเคชัน Bash

สำหรับคนใจร้อน: ข้อสรุปของ Nikita คือการใช้Batsแต่ดูเหมือนว่า Nikita พลาดโครงการBats-coreซึ่งดูเหมือนว่าฉันจะเป็นโครงการที่จะใช้ต่อไปเนื่องจากโครงการ Bats ดั้งเดิมไม่ได้รับการดูแลอย่างจริงจังตั้งแต่ปี 2013


7

อีพ็อกซี่เป็นกรอบการทดสอบทุบตีผมออกแบบเป็นหลักสำหรับการทดสอบซอฟแวร์อื่น ๆ แต่ฉันใช้มันเพื่อโมดูลทุบตีทดสอบเช่นกันรวมทั้งตัวเองและกล่อง

ข้อได้เปรียบหลักคือค่าใช้จ่ายในการเข้ารหัสที่ค่อนข้างต่ำการยืนยันแบบไม่ จำกัด และการเลือกการยืนยันที่ยืดหยุ่นในการตรวจสอบ

ฉันทำการนำเสนอเปรียบเทียบกับBeakerLibซึ่งเป็นกรอบงานที่ Red Hat ใช้


6

ทำไมคุณถึงบอกว่า "ยาก" ในการทดสอบสคริปต์ทุบตี

มีอะไรผิดปกติกับเสื้อคลุมทดสอบเช่น:

 #!/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
 }

4
ประการแรกสคริปต์ทุบตีไม่สามารถอ่านได้มากนัก ประการที่สองความคาดหวังมีความซับซ้อนเช่นการตรวจสอบว่าไฟล์ล็อกถูกสร้างขึ้นด้วย PID ของสคริปต์ bash ที่สร้างขึ้นหรือไม่
nimcap

10
ที่สำคัญกว่านั้นเป็นการยากที่จะทดสอบเชลล์สคริปต์เนื่องจากโดยทั่วไปมีผลข้างเคียงจำนวนมากและใช้ทรัพยากรระบบเช่นระบบไฟล์เครือข่าย ฯลฯ ตามหลักการแล้วการทดสอบหน่วยจะปราศจากผลข้างเคียงและไม่ขึ้นอยู่กับทรัพยากรระบบ
jayhendren

4

ฉันสร้างเชลล์สเปคเพราะต้องการเครื่องมือที่ใช้งานง่ายและมีประโยชน์

เขียนโดยเชลล์สคริปต์ POSIX บริสุทธิ์ มีการทดสอบกับเปลือกหอยหลายชนิดมากกว่า shunit2 มีคุณสมบัติที่ทรงพลังกว่า bats / bats-core

ตัวอย่างเช่นสนับสนุนบล็อกที่ซ้อนกันง่ายต่อการเยาะเย้ย / ต้นขั้วง่ายต่อการข้าม / รอดำเนินการการทดสอบพารามิเตอร์หมายเลขบรรทัดการยืนยันดำเนินการตามหมายเลขบรรทัดการดำเนินการแบบขนานการดำเนินการแบบสุ่มรูปแบบ TAP / JUnit การครอบคลุมและการรวม CI profiler และอื่น ๆ .

ดูการสาธิตได้ที่หน้าโครงการ


3

ฉันค่อนข้างชอบshell2junitซึ่งเป็นยูทิลิตี้ในการสร้างผลลัพธ์ที่เหมือน JUnit จากการทดสอบสคริปต์ Bash สิ่งนี้มีประโยชน์เนื่องจากรายงานที่สร้างขึ้นสามารถอ่านได้โดยระบบการรวมต่อเนื่องเช่นปลั๊กอิน JUnit สำหรับ Jenkins และ Bamboo

แม้ว่า shell2junit จะไม่มีกรอบการเขียนสคริปต์ Bash ที่ครอบคลุมเช่นshunit2แต่ก็ช่วยให้คุณสามารถรายงานผลการทดสอบได้ดี


3

ลองbashtest เป็นวิธีง่ายๆในการทดสอบสคริปต์ของคุณ ตัวอย่างเช่นคุณdo-some-work.shเปลี่ยนไฟล์กำหนดค่าบางไฟล์ ตัวอย่างเช่นเพิ่มบรรทัดใหม่ให้กับไฟล์PASSWORD = 'XXXXX' config/etc/my.cfg

คุณเขียนคำสั่ง bash ทีละบรรทัดจากนั้นตรวจสอบผลลัพธ์

ติดตั้ง:

pip3 install bashtest

สร้างการทดสอบเป็นเพียงการเขียนคำสั่งทุบตี

ไฟล์test-do-some-work.bashtest:

# run the script  
$ ./do-some-work.sh > /dev/null

# testing that the line "PASSWORD = 'XXXXX'" is in the file /etc/my.cfg   
$ grep -Fxq "PASSWORD = 'XXXXX'" /etc/my.cfg && echo "YES"
YES

เรียกใช้การทดสอบ:

bashtest *.bashtest

คุณสามารถดูตัวอย่างได้ที่นี่และที่นี่


3

สิ่งนี้อาจใช้หรือมีส่วนร่วมได้

https://thorsteinssonh.github.io/bash_test_tools/

ตั้งใจจะเขียนผลลัพธ์ในโปรโตคอล TAP ซึ่งฉันคิดว่าดีสำหรับ CI และดีสำหรับผู้ที่ต้องการสภาพแวดล้อมเชลล์ ฉันคิดว่าบางสิ่งบางอย่างทำงานในสภาพแวดล้อมเชลล์ดังนั้นบางอย่างอาจโต้แย้งว่าควรทดสอบในสภาพแวดล้อมเชลล์



3

ไม่อยากเชื่อเลยว่าไม่มีใครพูดถึงOSHT ! มันเข้ากันได้กับทั้ง TAP และ JUnit มันเป็นเปลือกที่บริสุทธิ์ (นั่นคือไม่มีภาษาอื่น ๆ ที่เกี่ยวข้อง) มันทำงานแบบสแตนด์อโลนด้วยและมันก็ง่ายและตรงไปตรงมา

การทดสอบมีลักษณะดังนี้ (ตัวอย่างที่นำมาจากหน้าโครงการ):

#!/bin/bash
. osht.sh

# Optionally, indicate number of tests to safeguard against abnormal exits
PLAN 13

# Comparing stuff
IS $(whoami) != root
var="foobar"
IS "$var" =~ foo
ISNT "$var" == foo

# test(1)-based tests
OK -f /etc/passwd
NOK -w /etc/passwd

# Running stuff
# Check exit code
RUNS true
NRUNS false

# Check stdio/stdout/stderr
RUNS echo -e 'foo\nbar\nbaz'
GREP bar
OGREP bar
NEGREP . # verify empty

# diff output
DIFF <<EOF
foo
bar
baz
EOF

# TODO and SKIP
TODO RUNS false
SKIP test $(uname -s) == Darwin

วิ่งง่ายๆ:

$ bash test.sh
1..13
ok 1 - IS $(whoami) != root
ok 2 - IS "$var" =~ foo
ok 3 - ISNT "$var" == foo
ok 4 - OK -f /etc/passwd
ok 5 - NOK -w /etc/passwd
ok 6 - RUNS true
ok 7 - NRUNS false
ok 8 - RUNS echo -e 'foo\nbar\nbaz'
ok 9 - GREP bar
ok 10 - OGREP bar
ok 11 - NEGREP . # verify empty
ok 12 - DIFF <<EOF
not ok 13 - TODO RUNS false # TODO Test Know to fail

การทดสอบครั้งล่าสุดแสดงเป็น "ไม่ตกลง" แต่รหัสทางออกคือ 0 เนื่องจากเป็นไฟล์TODO. สามารถตั้งค่า verbose ได้เช่นกัน:

$ OSHT_VERBOSE=1 bash test.sh # Or -v
1..13
# dcsobral \!= root
ok 1 - IS $(whoami) != root
# foobar =\~ foo
ok 2 - IS "$var" =~ foo
# \! foobar == foo
ok 3 - ISNT "$var" == foo
# test -f /etc/passwd
ok 4 - OK -f /etc/passwd
# test \! -w /etc/passwd
ok 5 - NOK -w /etc/passwd
# RUNNING: true
# STATUS: 0
# STDIO <<EOM
# EOM
ok 6 - RUNS true
# RUNNING: false
# STATUS: 1
# STDIO <<EOM
# EOM
ok 7 - NRUNS false
# RUNNING: echo -e foo\\nbar\\nbaz
# STATUS: 0
# STDIO <<EOM
# foo
# bar
# baz
# EOM
ok 8 - RUNS echo -e 'foo\nbar\nbaz'
# grep -q bar
ok 9 - GREP bar
# grep -q bar
ok 10 - OGREP bar
# \! grep -q .
ok 11 - NEGREP . # verify empty
ok 12 - DIFF <<EOF
# RUNNING: false
# STATUS: 1
# STDIO <<EOM
# EOM
not ok 13 - TODO RUNS false # TODO Test Know to fail

เปลี่ยนชื่อเป็นใช้.tส่วนขยายและวางไว้ในtไดเร็กทอรีย่อยและคุณสามารถใช้prove(1)(ส่วนหนึ่งของ Perl) เพื่อเรียกใช้:

$ prove
t/test.t .. ok
All tests successful.
Files=1, Tests=13,  0 wallclock secs ( 0.03 usr  0.01 sys +  0.11 cusr  0.16 csys =  0.31 CPU)
Result: PASS

ตั้งค่าOSHT_JUNITหรือส่งผ่าน-jเพื่อสร้างเอาต์พุต JUnit JUnit สามารถใช้ร่วมกับprove(1)ไฟล์.

ฉันได้ใช้ห้องสมุดนี้ทั้งฟังก์ชั่นการทดสอบโดยการจัดหาไฟล์ของพวกเขาแล้ววิ่งยืนยันกับIS/ OKและเชิงลบของพวกเขาและสคริปต์โดยใช้/RUN NRUNสำหรับฉันกรอบนี้ให้ผลประโยชน์สูงสุดสำหรับค่าใช้จ่ายที่น้อยที่สุด


1

ฉันได้ลองใช้วิธีแก้ปัญหามากมายที่นำเสนอที่นี่ แต่พบว่าส่วนใหญ่มีขนาดใหญ่และใช้งานยากดังนั้นฉันจึงสร้างกรอบการทดสอบของตัวเองขึ้นมา: https://github.com/meonlol/t-bash

เป็นเพียงไฟล์เดียวใน repo ที่คุณสามารถเรียกใช้โดยตรงโดยมีชุดพื้นฐานของสไตล์ JUnit ยืนยัน

ฉันใช้มันอย่างมืออาชีพในโครงการภายในหลายโครงการและสามารถทำให้สคริปต์ทุบตีของเรามีความเสถียรและทนต่อการถดถอยได้เป็นอย่างดี



0

ลองดูที่Outthenticมันง่ายขยายได้หลายภาษา (Perl, Python, Ruby, Bash ตามที่เลือก) และเฟรมเวิร์กข้ามแพลตฟอร์ม (Linux, Windows) เพื่อทดสอบแอปพลิเคชันบรรทัดคำสั่งใด ๆ


-2

ฉันพบว่ามันยากที่จะพิสูจน์โดยใช้ bash สำหรับสคริปต์ขนาดใหญ่เมื่อPythonมีข้อดีมากมายเช่นนี้:

  • ลอง / ยกเว้นอนุญาตให้เขียนสคริปต์ที่มีประสิทธิภาพมากขึ้นพร้อมความสามารถในการเลิกทำการเปลี่ยนแปลงในกรณีที่เกิดข้อผิดพลาด
  • คุณไม่จำเป็นต้องใช้ไวยากรณ์ที่คลุมเครือเช่น ' if [ x"$foo" = x"$bar"]; then ...' ซึ่งมีแนวโน้มที่จะเกิดข้อผิดพลาด
  • แยกวิเคราะห์ตัวเลือกและอาร์กิวเมนต์ได้ง่ายโดยใช้getoptโมดูล (และยังมีโมดูลที่ง่ายกว่าสำหรับการแยกวิเคราะห์อาร์กิวเมนต์ แต่ชื่อก็หลีกหนีฉันไป)
  • Python ช่วยให้คุณสามารถทำงานกับรายการ / คำสั่งและวัตถุแทนสตริงและอาร์เรย์พื้นฐาน
  • เข้าถึงเครื่องมือภาษาที่เหมาะสมเช่น regex ฐานข้อมูล (แน่ใจว่าคุณสามารถไพพ์ทุกอย่างลงในmysqlคำสั่ง bash ได้ แต่ไม่ใช่วิธีที่ดีที่สุดในการเขียนโค้ด)
  • ไม่ต้องกังวลเกี่ยวกับการใช้รูปแบบที่ถูกต้องของ$*หรือ"$*"หรือ"$@"หรือ$1หรือ"$1"ช่องว่างในชื่อไฟล์ไม่ใช่ปัญหา ฯลฯ ฯลฯ ฯลฯ

ตอนนี้ฉันใช้ bash สำหรับสคริปต์ที่ง่ายที่สุดเท่านั้น


3
ไม่ปฏิเสธความจริงที่ว่า Python มีข้อดี แต่จุดที่สองของคุณไม่ได้รับการยอมรับอย่างดี if [[ $foo = $bar ]]; then ...การเปรียบเทียบเดียวกันจะได้รับการดำเนินการเป็น นี่ยังไม่ดีไปกว่าสิ่งที่ python นำเสนอ แต่มันดีกว่าที่คุณนำเสนอ
Shrikant Sharat

8
บางระบบ (ฝังตัวสำหรับเช่น) ไม่มี python และคุณไม่สามารถ / ไม่ต้องการติดตั้งสิ่งเพิ่มเติม
Rui Marques

2
ฉันเองชอบทุบตี แต่ยอมรับว่ามันสามารถทดสอบได้เล็กน้อย โดยปกติคุณจะต้องดำเนินการเชิงรุกมากขึ้นในขณะที่ใน Python คุณสามารถแก้ไขข้อผิดพลาดได้หลังจากเกิดขึ้นแล้ว อย่างไรก็ตาม bash มีtrap(ในการล้างข้อมูล / เลิกทำในกรณีที่เกิดข้อผิดพลาด) เช่นเดียวกับ regex (เช่น[[ $1 =~ ^[1-3]{3}$ ]]) ฉันค่อนข้างแน่ใจว่าไวยากรณ์ที่คลุมเครือที่คุณใช้นั้นอ้างอิงถึงการใช้งานแบบเก่าtestไม่ใช่ bash Bash เหมาะสำหรับการเชื่อมต่อกับเครื่องมือบรรทัดคำสั่งที่มีอยู่ ... บ่อยครั้งที่ไปป์เดียวawkหรือgrepง่ายกว่าทางเลือก Python
หก

1
BTW โมดูล parser ที่คุณหมายถึงมีแนวโน้มหรือทายาทoptparse argparseฉันไม่เคยเห็นใครใช้getoptโมดูลนี้และฉันไม่ได้ใช้มันเป็นการส่วนตัว อย่างไรก็ตามgetoptยูทิลิตี้นั้นยอดเยี่ยมมาก การแยกอาร์กิวเมนต์จากเชลล์ไม่ใช่ปัญหาเลยเมื่อคุณมีรูปแบบที่ดี เว้นแต่คุณจะพยายามใช้คำสั่งย่อยสไตล์คอมไพล์หรือบางอย่างก็ไม่เป็นปัญหามากนัก
หก

Python จะไม่ทำงานทุกที่ที่ bash สามารถเข้าถึงได้ ฉันบอกว่าเป็นเพราะเราทดสอบ bash กับ python ตรรกะรหัสเดียวกันและขอให้ทั้งคู่ทำอะไรบางอย่าง Bash เข้าสู่ทุกไดเรกทอรีที่เข้าถึงได้ ในทางกลับกัน python ไม่สามารถจัดการกับการอนุญาตและไฟล์บางไดเร็กทอรีและไดเร็กทอรีที่เพิ่มขึ้นและลดลงอย่างรวดเร็ว
vianna77
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.