Jump to content
Wikipedia The Free Encyclopedia

Module:UnitTests

From Wikipedia, the free encyclopedia
Module documentation[view] [edit] [history] [purge]

UnitTests provides a unit test facility that can be used by other scripts using require. See Wikipedia:Lua#Unit_testing for details. The following is a sample from Module:Example/testcases:

-- Unit tests for [[Module:Example]]. Click talk page to run tests.
localp=require('Module:UnitTests')
functionp:test_hello()
self:preprocess_equals('{{#invoke:Example | hello}}','Hello World!')
end
returnp

The talk page Module talk:Example/testcases executes it with {{#invoke: Example/testcases | run_tests}}. Test methods like test_hello above must begin with "test".

Methods

run_tests

  • run_tests: Runs all tests. Normally used on talk page of unit tests.
    {{#invoke:Example/testcases|run_tests}}
    
  • If differs_at is specified, a column will be added showing the first character position where the expected and actual results differ.
    {{#invoke:Example/testcases|run_tests|differs_at=1}}
    
  • If highlight is specified, failed tests will be highlighted to make them easier to spot. A user script that moves failed tests to the top is also available.
    {{#invoke:Example/testcases|run_tests|highlight=1}}
    
  • If live_sandbox is specified, the header will show the columns "Test", "Live", "Sandbox", "Expected". This is required when using the preprocess_equals_sandbox_many method.

preprocess_equals

  • preprocess_equals(text, expected, options): Gives a piece of wikitext to preprocess and an expected resulting value. Scripts and templates can be invoked in the same manner they would be in a page.
    self:preprocess_equals('{{#invoke:Example | hello}}','Hello, world!',{nowiki=1})
    

preprocess_equals_many

  • preprocess_equals_many(prefix, suffix, cases, options): Performs a series of preprocess_equals() calls on a set of given pairs. Automatically adds the given prefix and suffix to each text.
    self:preprocess_equals_many('{{#invoke:Example | hello_to |','}}',{
    {'John','Hello, John!'},
    {'Jane','Hello, Jane!'},
    },{nowiki=1})
    

preprocess_equals_preprocess

  • preprocess_equals_preprocess(text, expected, options): Gives two pieces of wikitext to preprocess and determines if they produce the same value. Useful for comparing scripts to existing templates.
    self:preprocess_equals_preprocess('{{#invoke:Example | hello}}','{{Hello}}',{nowiki=1})
    

preprocess_equals_preprocess_many

  • preprocess_equals_preprocess_many(prefix1, suffix1, prefix2, suffix2, cases, options): Performs a series of preprocess_equals_preprocess() calls on a set of given pairs. The prefix/suffix supplied for both arguments is added automatically. If in any case the second part is not specified, the first part will be used.
    self:preprocess_equals_preprocess_many('{{#invoke:ConvertNumeric | numeral_to_english|','}}','{{spellnum','}}',{
    {'2'},-- equivalent to {'2','2'},
    {'-2','-2.0'},
    },{nowiki=1})
    

preprocess_equals_sandbox_many

  • preprocess_equals_sandbox_many(module, function, cases, options): Performs a series of preprocess_equals_compare() calls on a set of given pairs. The test compares the live version of the module vs the /sandbox version and vs an expected result. Ensure live_sandbox is specified or there may be some errors in the output.
    self:preprocess_equals_sandbox_many('{{#invoke:Example','hello_to',{
    {'John','Hello, John!'},
    {'Jane','Hello, Jane!'},
    },{nowiki=1})
    

equals

  • equals(name, actual, expected, options): Gives a computed value and the expected value, and checks if they are equal according to the == operator. Useful for testing modules that are designed to be used by other modules rather than using #invoke.
    self:equals('Simple addition',2+2,4,{nowiki=1})
    

equals_deep

  • equals_deep(name, actual, expected, options): Like equals, but handles tables by doing a deep comparison. Neither value should contain circular references, as they are not handled by the current implementation and may result in an infinite loop.
    self:equals_deep('Table comparison',createRange(1,3),{1,2,3},{nowiki=1})
    

Test options

These are the valid options that can be passed into the options parameters of the test functions listed above.

nowiki

Enabling this wraps the output text in <nowiki>...</nowiki> tags to avoid the text being rendered (E.g. <span>[[Example|Page]]</span> instead of Page )

combined

Only available in preprocess_equals and preprocess_equals_preprocess

Enabling this will display the output text in both the rendered mode and the nowiki mode to allow for both a raw text and visual comparison.

templatestyles

Only available in preprocess_equals and preprocess_equals_preprocess

Enabling this fixes the IDs in the strip markers <templatestyles>...</templatestyles> produces when processed to avoid incorrectly failing the tests.

stripmarker

Only available in preprocess_equals and preprocess_equals_preprocess

Enabling this fixes the IDs in all strip markers produces when processed to avoid incorrectly failing the tests.

display

Only available in equals

An optional function that changes how the output from the tests are displayed. This doesn't affect the comparison process.

See also

The above documentation is transcluded from Module:UnitTests/doc. (edit | history)
Editors can experiment in this module's sandbox (edit | diff) and testcases (edit | run) pages.
Subpages of this module.

 -- UnitTester provides unit testing for other Lua scripts. For details see [[Wikipedia:Lua#Unit_testing]].
 -- For user documentation see talk page.
 localUnitTester={}

 localframe,tick,cross,should_highlight
 localresult_table_header="{|class=\"wikitable unit-tests-result\"\n|+ %s\n! !! Text !! Expected !! Actual"
 localresult_table_live_sandbox_header="{|class=\"wikitable unit-tests-result\"\n|+ %s\n! !! Test !! Live !! Sandbox !! Expected"

 localresult_table={n=0}
 localresult_table_mt={
 insert=function(self,...)
 localn=self.n
 fori=1,select('#',...)do
 localval=select(i,...)
 ifval~=nilthen
 n=n+1
 self[n]=val
 end
 end
 self.n=n
 end,
 insert_format=function(self,...)
 self:insert(string.format(...))
 end,
 concat=table.concat
 }
 result_table_mt.__index=result_table_mt
 setmetatable(result_table,result_table_mt)

 localnum_failures=0
 localnum_runs=0

 localfunctionfirst_difference(s1,s2)
 s1,s2=tostring(s1),tostring(s2)
 ifs1==s2thenreturn''end
 localmax=math.min(#s1,#s2)
 fori=1,maxdo
 ifs1:sub(i,i)~=s2:sub(i,i)thenreturniend
 end
 returnmax+1
 end

 localfunctionreturn_varargs(...)
 return...
 end

 functionUnitTester:calculate_output(text,expected,actual,options)
 -- Set up some variables for throughout for ease
 num_runs=num_runs+1
 localoptions=optionsor{}

 -- Fix any stripmarkers if asked to do so to prevent incorrect fails
 localcompared_expected=expected
 localcompared_actual=actual
 ifoptions.templatestylesthen
 localpattern='(127円[^127円]*UNIQ%-%-templatestyles%-)(%x+)(%-QINU[^127円]*127円)'
 local_,expected_stripmarker_id=compared_expected:match(pattern)-- when module rendering has templatestyles strip markers, use ID from expected to prevent false test fail
 ifexpected_stripmarker_idthen
 compared_actual=compared_actual:gsub(pattern,'%1'..expected_stripmarker_id..'%3')-- replace actual id with expected id; ignore second capture in pattern
 compared_expected=compared_expected:gsub(pattern,'%1'..expected_stripmarker_id..'%3')-- account for other strip markers
 end
 end
 ifoptions.stripmarkerthen
 localpattern='(127円[^127円]*UNIQ%-%-%l+%-)(%x+)(%-%-?QINU[^127円]*127円)'
 local_,expected_stripmarker_id=compared_expected:match(pattern)
 ifexpected_stripmarker_idthen
 compared_actual=compared_actual:gsub(pattern,'%1'..expected_stripmarker_id..'%3')
 compared_expected=compared_expected:gsub(pattern,'%1'..expected_stripmarker_id..'%3')
 end
 end

 -- Perform the comparison
 localsuccess=compared_actual==compared_expected
 ifnotsuccessthen
 num_failures=num_failures+1
 end

 -- Sort the wikitext for displaying the results
 ifoptions.combinedthen
 -- We need 2 rows available for the expected and actual columns
 -- Top one is parsed, bottom is unparsed
 localdiffers_at=self.differs_atand(' \n| rowspan=2|'..first_difference(compared_expected,compared_actual))or''
 -- Local copies of tick/cross to allow for highlighting
 localhighlight=(should_highlightandnotsuccessand'style="background:#fc0;" ')or''
 result_table:insert(-- Start output
 '| ',highlight,'rowspan=2|',successandtickorcross,-- Tick/Cross (2 rows)
 ' \n| rowspan=2|',mw.text.nowiki(text),' \n| ',-- Text used for the test (2 rows)
 expected,' \n| ',actual,-- The parsed outputs (in the 1st row)
 differs_at,' \n|-\n| ',-- Where any relevant difference was (2 rows)
 mw.text.nowiki(expected),' \n| ',mw.text.nowiki(actual),-- The unparsed outputs (in the 2nd row)
 '\n|-\n'-- End output
 )
 else
 -- Display normally with whichever option was preferred (nowiki/parsed)
 localdiffers_at=self.differs_atand(' \n| '..first_difference(compared_expected,compared_actual))or''
 localformatting=options.nowikiandmw.text.nowikiorreturn_varargs
 localhighlight=(should_highlightandnotsuccessand'style="background:#fc0;"|')or''
 result_table:insert(-- Start output
 '| ',highlight,successandtickorcross,-- Tick/Cross
 ' \n| ',mw.text.nowiki(text),' \n| ',-- Text used for the test
 formatting(expected),' \n| ',formatting(actual),-- The formatted outputs
 differs_at,-- Where any relevant difference was
 '\n|-\n'-- End output
 )
 end
 end

 functionUnitTester:preprocess_equals(text,expected,options)
 localactual=frame:preprocess(text)
 self:calculate_output(text,expected,actual,options)
 end

 functionUnitTester:preprocess_equals_many(prefix,suffix,cases,options)
 for_,caseinipairs(cases)do
 self:preprocess_equals(prefix..case[1]..suffix,case[2],options)
 end
 end

 functionUnitTester:preprocess_equals_preprocess(text1,text2,options)
 localactual=frame:preprocess(text1)
 localexpected=frame:preprocess(text2)
 self:calculate_output(text1,expected,actual,options)
 end

 functionUnitTester:preprocess_equals_compare(live,sandbox,expected,options)
 locallive_text=frame:preprocess(live)
 localsandbox_text=frame:preprocess(sandbox)
 localhighlight_live=false
 localhighlight_sandbox=false
 num_runs=num_runs+1
 iflive_text==expectedandsandbox_text==expectedthen
 result_table:insert('| ',tick)
 else
 result_table:insert('| ',cross)
 num_failures=num_failures+1

 iflive_text~=expectedthen
 highlight_live=true
 end

 ifsandbox_text~=expectedthen
 highlight_sandbox=true
 end
 end
 localformatting=(optionsandoptions.nowikiandmw.text.nowiki)orreturn_varargs
 localdiffers_at=self.differs_atand(' \n| '..first_difference(expected,live_text)orfirst_difference(expected,sandbox_text))or''
 result_table:insert(
 ' \n| ',
 mw.text.nowiki(live),
 should_highlightandhighlight_liveand' \n|style="background: #fc0;"| 'or' \n| ',
 formatting(live_text),
 should_highlightandhighlight_sandboxand' \n|style="background: #fc0;"| 'or' \n| ',
 formatting(sandbox_text),
 ' \n| ',
 formatting(expected),
 differs_at,
 "\n|-\n"
 )
 end

 functionUnitTester:preprocess_equals_preprocess_many(prefix1,suffix1,prefix2,suffix2,cases,options)
 for_,caseinipairs(cases)do
 self:preprocess_equals_preprocess(prefix1..case[1]..suffix1,prefix2..(case[2]andcase[2]orcase[1])..suffix2,options)
 end
 end

 functionUnitTester:preprocess_equals_sandbox_many(module,function_name,cases,options)
 for_,caseinipairs(cases)do
 locallive=module.."|"..function_name.."|"..case[1].."}}"
 localsandbox=module.."/sandbox|"..function_name.."|"..case[1].."}}"
 self:preprocess_equals_compare(live,sandbox,case[2],options)
 end
 end

 functionUnitTester:equals(name,actual,expected,options)
 num_runs=num_runs+1
 ifactual==expectedthen
 result_table:insert('| ',tick)
 else
 result_table:insert('| ',cross)
 num_failures=num_failures+1
 end
 localformatting=(optionsandoptions.nowikiandmw.text.nowiki)orreturn_varargs
 localdiffers_at=self.differs_atand(' \n| '..first_difference(expected,actual))or''
 localdisplay=optionsandoptions.displayorreturn_varargs
 result_table:insert(' \n| ',name,' \n| ',
 formatting(tostring(display(expected))),' \n| ',
 formatting(tostring(display(actual))),differs_at,"\n|-\n")
 end

 localfunctiondeep_compare(t1,t2,ignore_mt)
 localty1=type(t1)
 localty2=type(t2)
 ifty1~=ty2thenreturnfalseend
 ifty1~='table'andty2~='table'thenreturnt1==t2end

 localmt=getmetatable(t1)
 ifnotignore_mtandmtandmt.__eqthenreturnt1==t2end

 fork1,v1inpairs(t1)do
 localv2=t2[k1]
 ifv2==nilornotdeep_compare(v1,v2)thenreturnfalseend
 end
 fork2,v2inpairs(t2)do
 localv1=t1[k2]
 ifv1==nilornotdeep_compare(v1,v2)thenreturnfalseend
 end

 returntrue
 end

 localfunctionval_to_str(obj)
 localfunctiontable_key_to_str(k)
 iftype(k)=='string'andmw.ustring.match(k,'^[_%a][_%a%d]*$')then
 returnk
 else
 return'['..val_to_str(k)..']'
 end
 end

 iftype(obj)=="string"then
 obj=mw.ustring.gsub(obj,"\n","\\n")
 ifmw.ustring.match(mw.ustring.gsub(obj,'[^\'"]',''),'^"+$')then
 return"'"..obj.."'"
 end
 return'"'..mw.ustring.gsub(obj,'"','\\"')..'"'

 elseiftype(obj)=="table"then
 localresult,checked={},{}
 fork,vinipairs(obj)do
 table.insert(result,val_to_str(v))
 checked[k]=true
 end
 fork,vinpairs(obj)do
 ifnotchecked[k]then
 table.insert(result,table_key_to_str(k)..'='..val_to_str(v))
 end
 end
 return'{'..table.concat(result,',')..'}'

 else
 returntostring(obj)
 end
 end

 functionUnitTester:equals_deep(name,actual,expected,options)
 num_runs=num_runs+1
 ifdeep_compare(actual,expected)then
 result_table:insert('| ',tick)
 else
 result_table:insert('| ',cross)
 num_failures=num_failures+1
 end
 localformatting=(optionsandoptions.nowikiandmw.text.nowiki)orreturn_varargs
 localactual_str=val_to_str(actual)
 localexpected_str=val_to_str(expected)
 localdiffers_at=self.differs_atand(' \n| '..first_difference(expected_str,actual_str))or''
 result_table:insert(' \n| ',name,' \n| ',formatting(expected_str),
 ' \n| ',formatting(actual_str),differs_at,"\n|-\n")
 end

 functionUnitTester:iterate(examples,func)
 require'libraryUtil'.checkType('iterate',1,examples,'table')
 iftype(func)=='string'then
 func=self[func]
 elseiftype(func)~='function'then
 error(("bad argument #2 to 'iterate' (expected function or string, got %s)")
 :format(type(func)),2)
 end

 fori,exampleinipairs(examples)do
 iftype(example)=='table'then
 func(self,unpack(example))
 elseiftype(example)=='string'then
 self:heading(example)
 else
 error(('bad example #%d (expected table, got %s)')
 :format(i,type(example)),2)
 end
 end
 end

 functionUnitTester:heading(text)
 result_table:insert_format(' ! colspan="%u" style="text-align: left" | %s \n |- \n ',
 self.columns,text)
 end

 functionUnitTester:run(frame_arg)
 frame=frame_arg
 self.frame=frame
 self.differs_at=frame.args['differs_at']
 tick=frame:preprocess('{{Tick}}')
 cross=frame:preprocess('{{Cross}}')

 localtable_header=result_table_header
 ifframe.args['live_sandbox']then
 table_header=result_table_live_sandbox_header
 end
 ifframe.args.highlightthen
 should_highlight=true
 end

 self.columns=4
 ifself.differs_atthen
 table_header=table_header..' !! Differs at'
 self.columns=self.columns+1
 end

 -- Sort results into alphabetical order.
 localself_sorted={}
 forkey,_inpairs(self)do
 ifkey:find('^test')then
 table.insert(self_sorted,key)
 end
 end
 table.sort(self_sorted)
 -- Add results to the results table.
 for_,valueinipairs(self_sorted)do
 result_table:insert_format("<h2>%s</h2>\n",value)
 result_table:insert_format(table_header.."\n|-\n",value)
 self[value](self)
 result_table:insert("|}\n")
 end

 return(num_runs==0and"<b>No tests were run.</b>"
 ornum_failures==0and"<b style=\"color:#008000\">All "..num_runs.." tests passed.</b>"
 or"<b style=\"color:#800000\">"..num_failures.." of "..num_runs.." tests failed.</b>[[Category:Failed Lua testcases using Module:UnitTests]]"
 ).."\n\n"..frame:preprocess(result_table:concat())
 end

 functionUnitTester:new()
 localo={}
 setmetatable(o,self)
 self.__index=self
 returno
 end

 localp=UnitTester:new()
 functionp.run_tests(frame)returnp:run(frame)end
 returnp

AltStyle によって変換されたページ (->オリジナル) /