A Ruby programozási nyelv

Helyesség

Bevezetés

Az Eiffeléhez és Alphardéhoz hasonló helyesség-ellenőrzési módszer nincs a nyelvben. Támogatja viszont az exception-helyességet, azaz a típusinvariáns megőrzésre van eszköz. A begin-end blokkoknak lehet ensure kitétele, ami a blokk minden lefutása után garantáltan végrehajtódik.

begin # a blokk tényleges tartalma rescue # kivételkezelő rész else # lefut, ha nem váltódott ki exception ensure # itt lehetséges a típusinvariáns helyreállítása end

A profiler

A profiler megmutatja, hogy hányszor lett meghívva egy metódus és mennyi ideig tartott a futása. Futtassuk a programot -r profile kapcsolóval, vagy a kódba írjuk be a következő sort: require 'profile' Íme egy példa a Profiler használatára, melyben megszámoljuk a 12 karakter hosszú szavakat:

require 'profile' count = 0 words = File.open("/usr/share/dict/words") while word = words.gets word = word.chomp! if word.length == 12 count += 1 end end puts "#{count} twelve-character words"

és az eredmény:
20460 twelve-character words % cumulative self self total time seconds seconds calls ms/call ms/call name 7.76 12.01 12.01 234937 0.05 0.05 String#chomp! 7.75 24.00 11.99 234938 0.05 0.05 IO#gets 7.71 35.94 11.94 234937 0.05 0.05 String#length 7.62 47.74 11.80 234937 0.05 0.05 Fixnum#== 0.59 48.66 0.92 20460 0.04 0.04 Fixnum#+ 0.01 48.68 0.02 1 20.00 20.00 Profiler__.start_profile 0.00 48.68 0.00 1 0.00 0.00 File#initialize 0.00 48.68 0.00 1 0.00 0.00 Fixnum#to_s 0.00 48.68 0.00 1 0.00 0.00 File#open 0.00 48.68 0.00 1 0.00 0.00 Kernel.puts 0.00 48.68 0.00 2 0.00 0.00 IO#write 0.00 48.68 0.00 1 0.00 154800.00 #toplevel

A profile használatával a program futási ideje a sokszorosára nő, azonban a relatív időkből így is következtethetünk a kód számításigényes részeire.

Unit Tesztek

Test::Unit Framework az alábbi három főbb szolgáltatást nyújtja:

  1. Eszközt nyújt különálló tesztek írása
  2. Framework-t nyújt a tesztek strukturálására
  3. Rugalmas módszert biztosít a tesztek futtatására

Assertions == Várt eredmények

Nézzünk a használatára egy példát melyben arab számokat konvertálunk római számokká:
A példakód:
class Roman MAX_ROMAN = 4999 def initialize(value) if value <= 0 || value > MAX_ROMAN fail "Roman values must be > 0 and <= #{MAX_ROMAN}" end @value = value end FACTORS = [["m", 1000], ["cm", 900], ["d", 500], ["cd", 400], ["c", 100], ["xc", 90], ["l", 50], ["xl", 40], ["x", 10], ["ix", 9], ["v", 5], ["iv", 4], ["i", 1]] def to_s value = @value roman = "" for code, factor in FACTORS count, value = value.divmod(factor) roman << code unless count.zero? end roman end end

Első teszt:
require 'roman' require 'test/unit' class TestRoman < Test::Unit::TestCase def test_simple assert_equal("i", Roman.new(1).to_s) assert_equal("ix", Roman.new(9).to_s) end end

és kimenete:
Loaded suite - Started . Finished in 0.003655 seconds. 1 tests, 2 assertions, 0 failures, 0 errors

Az első assertion szerint az 1-re mint inputra "i"-t kell kapnunk, második assertion-re hasonlóan.
Második teszt:
require 'roman' require 'test/unit' class TestRoman < Test::Unit::TestCase def test_simple assert_equal("i", Roman.new(1).to_s) assert_equal("ii", Roman.new(2).to_s) assert_equal("iii", Roman.new(3).to_s) assert_equal("iv", Roman.new(4).to_s) assert_equal("ix", Roman.new(9).to_s) end end

és kimenete:
Loaded suite - Started F Finished in 0.021877 seconds. 1) Failure: <"ii"> expected but was <"i">. 1 tests, 2 assertions, 1 failures, 0 errors test_simple(TestRoman) [prog.rb:6]:

Ezek szerint "i" helyett "ii"-t kaptunk. A Teszt eset forráskódjából következtethetünk a hiba helyére (to_s metódus)
Tesztelhetjük, hogy egy adott kivétel kiváltódott-e?
require 'roman' require 'test/unit' class TestRoman < Test::Unit::TestCase def test_range assert_raise(RuntimeError) { Roman.new(0) } assert_nothing_raised() { Roman.new(1) } assert_nothing_raised() { Roman.new(499) } assert_raise(RuntimeError) { Roman.new(5000) } end end

és kimenete:
Loaded suite - Started . Finished in 0.002898 seconds. 1 tests, 4 assertions, 0 failures, 0 errors

Tesztek strukturálása

require 'test/unit'

A Unit teszteket természetes módon nagyobb egységekbe foglalhatjuk, ezek a teszt esetek. A teszt esetek általában egy adott funkció összes tesztjét tartalmazza.

A tesztet tartalmazó osztály ősének a Test::Unit::TestCase osztálynak kell lennie.
A metódusok amelyek az assertion-öket tartalmazzák "test"-el kell kezdődniük. Ez fontos ugyanis a Test::Unit ilyen kezdetű metódusokat vesz észre.
Ha több metódus is szerepel egy tesztben akkor mindegyik kialakítja a tesztkörnyezetet majd kitakarít maga után. Ilyen esetekben használhatjuk a speciális setup, teardown metódusokat, amelyek minden függvény előtt illetve után végrehajtódnak.

Tesztek Futtatása


$ ruby test_roman.rb

Loaded suite test_roman Started .. Finished in 0.039257 seconds. 2 tests, 9 assertions, 0 failures, 0 errors

Amint látjuk a Test::Unit lefuttatja az össze teszt esetet. Ha csak egy bizonyos tesztesetre vagyunk kíváncsiak akkor ennek az osztálynak a nevét meg is adhatjuk:
$ ruby test_roman.rb --name test_range

A teszteket a "test" könyvtárba szokás tenni.

A lehetséges assertion-ök
assert(boolean, [ message ] )
	Megbukik ha a boolean false vagy nil.
assert_nil(obj, [ message ] )
assert_not_nil(obj, [ message ] )
	Az obj-nak nil-nek (nem nil-nek) kell lennie.
assert_equal(expected, actual, [ message ] )
assert_not_equal(expected, actual, [ message ] )
	Az expected és actual értékeknek egyezniük kell (vagy nem) az op== -t használva.
assert_in_delta(expected_float, actual_float, delta, [ message ] )
	Az actual_float értékének az expected_float delta sugarú környezetébe kell esnie.
assert_raise(Exception, . . . ) { block }
assert_nothing_raised(Exception, . . . ) { block }
	A block kivált (vagy nem vált ki) kivételt a felsoroltak közül.
assert_instance_of(klass, obj, [ message ] )
assert_kind_of(klass, obj, [ message ] )
	Az obj klass osztályúnak/típusúnak kell lennie.
assert_respond_to(obj, message, [ message ] )
	Az obj-nak tudni kell válaszolni a message üzenetre (szimbólum).
assert_match(regexp, string, [ message ] )
assert_no_match(regexp, string, [ message ] )
	A stringnek meg kell egyeznie (nem kell) a megadott reguláris kifejezéssel.
assert_same(expected, actual, [ message ] )
assert_not_same(expected, actual, [ message ] )
	Igaz (vagy hamis) az expected.equal?(actual).
assert_operator(obj1, operator, obj2, [ message ] )
	Az obj1-nek küldött obj2 paraméterű operátor üzenetnek igaznak kell lennie.
assert_throws(expected_symbol, [ message ] ) { block }
	A block-nak az expected_symbol -al adott kivételt kell dobnia.
assert_send(send_array, [ message ] )
	A send_array[1]-ben tárolt üzenetet elküldve send_array[0]-nak igazat kell adnia (send_array többi része paraméter).
żunk(message="Flunked")
    Mindíg megbukik.

Debugger

Debugger indítása:
ruby -r debug [options] [programfile] [arguments]

Szokásos eszközöket nyújtja: step; brakepoint; step into, step over methods; stack frame, változók, memberfüggvények kiírása, threadek listázása.

rdb promptnál kiadott help parancs hatására láthatjuk az alábbi táblázatot:

Debugger commands:
	b[reak] [file:]line 	breakpoint-ot állít be a megadott sorhoz a fileban (alapértelmezettként az aktuális sor).
	b[reak] [file:]name 	breakpoint-ot állít a megadott metódushoz a fileban.
	b[reak] 	Kilistázza a megadott breakpoint-okat és watchpoint-okat.
	wat[ch] expr 	Break ha az expression igazzá válik.
	del[ete] [nnn] 	Törli a breakpoint-ot (nnn) (alapértelmezettként az összeset).
	disp[lay] expr 	Kiírja az expr értékét valahányszor a debuggeré az irányításl.
	disp[lay] 	Megmutatja display-eket.
	undisp[lay] [nnn] 	Törli a display-t (nnn) (alapértelmezettként az összeset).
	c[ont] 	Folytatja a végrehajtást.
	s[tep] nnn=1 	Végrehajtja a következő nnn sort, belelépve a metódusokba.
	n[ext] nnn=1 	Végrehajtja a következő nnn sort, átlépve a metódusokat.
	fi[nish] 	Végrehajtja az aktuális függvényt.
	q[uit] 	Kilépés a debugger-ből.
	w[here] 	Kiírja az aktuális stack frame-et.
	f[rame] 	where szinonímája.
	l[ist] [start--end] 	Kilistázza a sorokat start-tól end-ig.
	up nnn=1 	Feljebb lép nnn szintet a stack frame-ben.
	down nnn=1 	Lejebb lép nnn szintet a stack frame-ben.
	v[ar] g[lobal] 	Kiírja a globális változókat.
	v[ar] l[ocal] 	Kiírja a lokális változókat.
	v[ar] i[stance] obj 	Kiírja az obj példányváltozóit.
	v[ar] c[onst] Name 	Kiírja a Name nevű class vagy modul konstansait.
	m[ethod] i[nstance] obj 	Kiírja az obj példány metódusait.
	m[ethod] Name 	Kiírja a Name class vagy modul példánymetódusait.
	th[read] l[ist] 	Kilistázza az összes futó thread-et.
	th[read] [c[ur[rent]]] 	Kiírja az aktuális thread státuszát.
	th[read] [c[ur[rent]]] nnn 	nnn thread legyen az aktuális és állítsa meg.
	th[read] stop nnn nnn thread legyen az aktuális és állítsa meg.
 	th[read] resume nnn 	Folytassa az nnn thread-et.
	[p] expr 	Értékelje ki az expr utasítást az aktuális környezetben.
	empty 	Üres parancs az utolsó parancsot ismétli.

A debug promptból az exit paranccsal léphetünk ki.

Egy szemléletes példa:
% ruby -r debug sample.rb (rdb:1) list 1-9 [1, 10] in t.rb => 1 def fact(n) 2 if n <= 0 3 1 4 else 5 n * fact(n-1) 6 end 7 end 8 9 p fact(5) (rdb:1) b 2 Set breakpoint 1 at t.rb:2 (rdb:1) c breakpoint 1, fact at t.rb:2 t.rb:2: if n <= 0 (rdb:1) disp n 1: n = 5 (rdb:1) del 1 (rdb:1) watch n==1 Set watchpoint 2 (rdb:1) c watchpoint 2, fact at t.rb:fact t.rb:1:def fact(n) 1: n = 1 (rdb:1) where --> #1 t.rb:1:in `fact' #2 t.rb:5:in `fact' #3 t.rb:5:in `fact' #4 t.rb:5:in `fact' #5 t.rb:5:in `fact' #6 t.rb:9 (rdb:1) del 2 (rdb:1) c 120