;+ ; Override in subclasses to perform setup actions before each test. ;- pro mgtestcase::setup compile_opt strictarr end ;+ ; Override in subclasses to perform teardown actions after each test. ;- pro mgtestcase::teardown compile_opt strictarr end ;+ ; This is a safe place to actually run a single test. Any errors that occur are ; assumed to be from the test and recorded as a failure for it. ; ; @returns boolean ; @param testname {in}{required}{type=string} name of method ; @keyword message {out}{optional}{type=string} error message if test failed ;- function mgtestcase::runTest, testname, message=msg compile_opt strictarr, logical_predicate error = 0L catch, error if (error ne 0L) then begin catch, /cancel msg = !error_state.msg return, 0L ; fail endif result = call_method(testname, self) if (~result) then msg = !error_state.msg return, keyword_set(result) end ;+ ; Run the tests for this class (i.e. methods with names that start with "test"). ;- pro mgtestcase::run compile_opt strictarr, logical_predicate self.testRunner->reportTestCaseStart, obj_class(self), ntests=self.ntests, $ level=self.level ; run each test for t = 0L, self.ntests - 1L do begin self.testRunner->reportTestStart, (*self.testnames)[t], level=self.level self->setup result = self->runTest((*self.testnames)[t], message=msg) if (result) then ++self.npass else ++self.nfail ; remove prefix from msg if present prefix = obj_class(self) + '::' + (*self.testnames)[t] + ': ' if (n_elements(msg) gt 0 && strpos(msg, prefix) eq 0) then begin prefixLength = strlen(prefix) msg = strmid(msg, prefixLength) endif ; remove ASSERT from msg if present prefix = 'ASSERT: ' if (n_elements(msg) gt 0 && strpos(msg, prefix) eq 0) then begin prefixLength = strlen(prefix) msg = strmid(msg, prefixLength) endif ; construct the log message for the test logMsg = result $ ? '' $ : (n_elements(msg) eq 0 $ ? '' $ : msg) self->teardown self.testRunner->reportTestResult, logMsg, passed=result endfor self.testRunner->reportTestCaseResult, npass=self.npass, $ nfail=self.nfail, $ level=self.level end ;+ ; Find the name and number of tests (i.e. methods with names that start with ; "test"). ;- pro mgtestcase::findTestnames compile_opt strictarr ; find tests: any method with name test* help, /routines, output=routines functionsPos = where(strmatch(routines, 'Compiled Functions:'), count) routines = routines[functionsPos:*] result = stregex(routines, '^' + obj_class(self) + '::(test[^ ]*).*', $ /extract, /subexpr, /fold_case) testnames = reform(result[1, *]) ; find names that matched ind = where(testnames ne '', ntests) if (ntests gt 0) then begin testnames = testnames[ind] endif ; record results self.ntests = ntests *self.testnames = testnames end ;+ ; Get properties of the object. ; ; @keyword npass {out}{optional}{type=integer} number of passing tests ; @keyword nfail {out}{optional}{type=integer} number of failing tests ; @keyword ntests {out}{optional}{type=integer} number of tests ; @keyword testnames {out}{optional}{type=strarr} array of method names which ; begin with "test" ;- pro mgtestcase::getProperty, npass=npass, nfail=nfail, ntests=ntests, $ testnames=testnames compile_opt strictarr npass = self.npass nfail = self.nfail ntests = self.ntests if (arg_present(testnames)) then testnames = *self.testnames end ;+ ; Test suites can contain other test suites or test cases. The level is the ; number of layers down from the top most test suite (level 0). ; ; @param level {in}{required}{type=integer} new level of object ;- pro mgtestcase::setLevel, level compile_opt strictarr self.level = level end ;+ ; Free resources. ;- pro mgtestcase::cleanup compile_opt strictarr ptr_free, self.testnames end ;+ ; Intialize test case. ; ; @returns 1 for succcess, 0 for failure ; @keyword test_runner {in}{required}{type=object} subclass of MGtestRunner ;- function mgtestcase::init, test_runner=testRunner compile_opt strictarr self.testRunner = testRunner self.testnames = ptr_new(/allocate_heap) self->findTestnames self.level = 0L self.npass = 0L self.nfail = 0L return, 1B end ;+ ; Define member variables. ; ; @file_comments Subclass MGtestCase to actually write tests. Any function ; method whose name starts with "test" will be considered a ; test. Tests are executed and results are reported to the test ; runner object. ; ; @field testRunner subclass of MGtestRunner ; @field testnames pointer to string array of method names that start with "test" ; @field level number of layers down from the top-containing suite ; @field ntests total number of tests ; @field npass number of passing tests ; @field nfail number of failing tests ;- pro mgtestcase__define compile_opt strictarr define = { MGtestCase, $ testRunner : obj_new(), $ testnames : ptr_new(), $ level : 0L, $ ntests : 0L, $ npass : 0L, $ nfail : 0L $ } end