======= CHANGES ======= Release 0.11.0 -------------- * [change] 'spec()' is now NOT obsoleted. * [change] 'spec()' is now available as function decorator. ex:: class FooTest(unittest.TestCase): def test_method1(self) @spec("1+1 should be 2") def _(): ok (1+1) == 2 @spec("1-1 should be 0") def _(): ok (1-1) == 0 * [enhance] New assertions: not_file(), not_dir() and not_exist(). ex:: ok (".").not_file() # same as NG (".").is_file() ok (__file__).not_dir() # same as NG (__file__).is_dir() ok ("foobar").not_exist() # same as NG ("foobar").exists() * [enhance] New assertion: not_match(). ex:: ok ("SOS").not_match(r"\d+") # same as NG ("SOS").matches(r"\d+") * [enhance] Global provider/releaser functions can take 'self' argument. ex:: def provide_logname(self): self._LOGNAME = os.getenv('LOGNAME') os.environ['LOGNAME'] = "Haruhi" return os.environ['LOGNAME'] def release_logname(self, value): os.environ['LOGNAME'] = self._LOGNAME * [change] Change not to ignore test classes which name starts with '_'. * [change] (internal) Move some utility functions to 'util' module. * [change] (internal) Move '_Context' and '_RunnableContext' classes into 'util' module. * [change] (internal) Move 'Color' class into 'util' module * [change] (internal) Remove 'OUT' variable in 'Reporter' class * [change] (internal) Move 'TARGET_PATTERN' variable to 'config' * [bugfix] Fix to clear ImportError after trying to import unittest2 Release 0.10.0 -------------- * [change] 'oktest.spec()' is obsoleted completely. It will print warning message if you use it. * [change] 'oktest.helper' module is renamed to 'oktest.util'. ('oktest.helper' is still available for backward compabibility.) * [enhance] Add 'oktest.main()' which is a replacement of 'oktest.run()'. Using 'oktest.main()' instead of 'oktest.run()', command options are available. ex:: ## for example: $ python test/foobar_test.py -sp -f test='*keyword*' ## is almost same as: $ python -m oktest test/foobar_test.py -sp -f test='*keyword*' * [enhance] Add 'oktest.fail(message)' which is same as 'unittest.fail(message)'. ex:: from oktest import fail fail("not impelmented yet") # will raise AssertionError * [enhance] (Experimental) Add '@todo' decorator which is equivarent to '@unittest.expectedFailure'. ex:: from oktest import ok, test, todo def add(x, y): return 0 # not implemented yet! class AddTest(unittest.TestCase): @test("returns sum of arguments.") @todo # equivarent to @unittest.expectedFailure def _(self): ok (10, 20) == 30 ## will be failed expectedly ## (because not implemented yet) Expected failure of assertion is reported as '[TODO]', not '[Failed]'. * [enhance] (Experimental) Test context supported. It helps you to describe specification in structured style. ex:: from oktest import ok, test from oktest import subject, situation class SampleTestCase(unittest.TestCase): SUBJECT = "class 'Sample'" with subject("method1()"): with situation("when condition:"): @test("spec1") def _(self): ... @test("spec2") def _(self): ... with situation("else:"): @test("spec3") def _(self): ... if __name__ == '__main__': import oktest oktest.main() Output exmple:: $ python test/example_test.py * class 'Sample' + method1() + when condition: - [passed] spec1 - [passed] spec2 + else: - [passed] spec3 ## total:3, passed:3, failed:0, error:0, skipped:0, todo:0 (0.000 sec) * [change] Output is changed. ### ### previous version ### $ python test/foo_test.py * FooTest - [ok] test1 - [ok] test2 - [skipped] test3 ## total:3, passed:2, failed:0, error:0, skipped:1 (elapsed 0.000) ### ### in this release ### $ python test/foo_test.py * FooTest - [passed] test1 - [passed] test2 - [skipped] test3 (reason: REASON) ## total:3, passed:2, failed:0, error:0, skipped:1, todo:0 (0.000 sec) Release 0.9.0 ------------- * New '@test' decorator provided. It is simple but very powerful. Using @test decorator, you can write test description in free text instead of test method. ex:: class FooTest(unittest.TestCase): def test_1_plus_1_should_be_2(self): # not cool... self.assertEqual(2, 1+1) @test("1 + 1 should be 2") # cool! easy to read & write! def _(self): self.assertEqual(2, 1+1) * Fixture injection support by '@test' decorator. Arguments of test method are regarded as fixture names and they are injected by @test decorator automatically. Instance methods or global functions which name is 'provide_xxxx' are regarded as fixture provider (or builder) for fixture 'xxxx'. ex:: class SosTest(unittest.TestCase): ## ## fixture providers. ## def provide_member1(self): return {"name": "Haruhi"} def provide_member2(self): return {"name": "Kyon"} ## ## fixture releaser (optional) ## def release_member1(self, value): assert value == {"name": "Haruhi"} ## ## testcase which requires 'member1' and 'member2' fixtures. ## @test("validate member's names") def _(self, member1, member2): assert member1["name"] == "Haruhi" assert member2["name"] == "Kyon" Dependencies between fixtures are resolved automatically. ex:: class BarTest(unittest.TestCase): ## ## for example: ## - Fixture 'a' depends on 'b' and 'c'. ## - Fixture 'c' depends on 'd'. ## def provide_a(b, c): return b + c + ["A"] def provide_b(): return ["B"] def provide_c(d): return d + ["C"] def provide_d(): reutrn ["D"] ## ## Dependencies between fixtures are solved automatically. ## @test("dependency test") def _(self, a): assert a == ["B", "D", "C", "A"] If loop exists in dependency then @test reports error. If you want to integrate with other fixture library, see the following example:: class MyFixtureManager(object): def __init__(self): self.values = { "x": 100, "y": 200 } def provide(self, name): return self.values[name] def release(self, name, value): pass oktest.fixure_manager = MyFixtureResolver() * Supports command-line interface to execute test scripts. ex:: ## run test scripts except foo_*.py in plain style $ python -m oktest -x 'foo_*.py' -sp tests/*_test.py ## run test scripts in 'tests' dir with pattern '*_test.py' $ python -m oktest -p '*_test.py' tests ## filter by class name $ python -m oktest -f class='ClassName*' tests ## filter by test method name $ python -m oktest -f test='*keyword*' tests ## filter by user-defined option added by @test decorator $ python -m oktest -f tag='*value*' tests Try ``python -m oktest -h`` for details about command-line options. * Reporting style is changed. Oktest now provides three reporing styles. - plain (similar to unittest) - simple - verbose (default) All of these styles are colored to emphasize errors. If you want change reporting style, specify ``-r`` option in command-line. * New assertion method ``ok(x).attr(name, value)`` to check attribute. ex:: d = datetime.date(2000, 12, 31) ok (d).attr('year', 2000).attr('month', 12).attr('date', 31) * New assertion method ``ok(x).length(n)``. This is same as ``ok(len(x)) == n``, but it is useful when chaining assertion methods. ex:: ok (func()).is_a(tuple).length(2) * New feature``ok().should`` helps you to check boolean method. ex:: ## same as ok ("Haruhi".startswith("Haru")) == True ok ("Haruhi").should.startswith("Haru") ## same as ok ("Haruhi".isupper()) == False ok ("Haruhi").should_not.isupper() * 'ok(str1) == str2' displays diff if text1 != text2, even when using with unittest module. In previous version, text diff is displayed only when using oktest.run(). If you are unittest user and using Python < 2.7, use 'ok(str1) == str2' instead of 'self.assertEqual(str2, str1)' to display text diff. * Assertion ``raises()`` supports regular expression to check error message. def fn(): raise ValueError("ERROR-123") ok (fn).raises(ValueError, re.compile(r'^[A-Z]+-\d+$')) * Helper functions in oktest.dummy module are now available as decorator. This is useful when you must use Python 2.4:: from oktest.dummy import dummy_io ## for Python 2.4 @dummy_io("SOS") def d_io(): assert sys.stdin.read() == "SOS" print("Haruhi") sout, serr = d_io assert sout == "Haruhi\n" ## for Python 2.5 or later with dummy_io("SOS") as d_io: assert sys.stdin.read() == "SOS" print("Haruhi") sout, serr = d_io assert sout == "Haruhi\n" * 'AssertionObject.expected' is renamed to 'AssertionObject.boolean'. You should update your custom assertion definition. ex:: import oktest @oktest.assertion def startswith(self, arg): boolean = self.target.startswith(arg) #if boolean == self.expected: # obsolete if boolean == self.boolean: return True self.failed("%r.startswith(%r) : failed." % (self.target, arg)) * ``oktest.run()`` is changed to return number of failures and errors of tests.:: sys.exit(oktest.run()) # status code == number of failures and errors * ``before_each()`` and ``after_each()`` are now non-supported. Use ``before()`` and ``after()`` intead. * (Experimental) New function ``NOT()`` provided which is same as ``NG()``. * (Experimental) ``skip()`` and ``@skip.when()`` are provided to skip tests:: from oktest import skip class FooTest(unittest.TestCase): def test_1(self): if sys.version.startswith('2.'): reason = "not available on Python 2.x" skip(reason) # raises SkipTest ... @skip.when(sys.version.startswith('2.'), "not available on Python 2.x") def test_2(self): ... If you want to use @skip.when with @test decorator, see the folloing:: ## OK @test("description") @skip.when(condition, "reason") def _(self): ... ## NG @skip.when(condition, "reason") @test("description") def _(self): ... Release 0.8.0 ------------- * add ``NG()`` which is same as not_ok(). * enhanced to proive egg files for Python 3. * enhanced to support assertion method chaining. :: ok ("sos".upper()).is_a(str).matches(r'^[A-Z]+$') == "SOS" * ``ok().matches()`` can take flag parameter which is passed to re.compile(). ok ("\nSOS\n").matches(r'^[A-Z]+$', re.M) ## same as: #ok("\nSOS\n").matches(r.compile(r'^[A-Z]$', re.M)) * enhance helper methods to be available without with-statement. (this is necessary for Python 2.4 which is default version on CentOS.) from oktest.helper import chdir def fn(): ok (os.getcwd()) == "/tmp" chdir("/tmp").run(fn) ## this is same as: #with chdir("/tmp"): # ok (os.getcwd()) == "/tmp" from oktest.dummy import dummy_file def fn(): ok ("A.txt").is_file() ok (open("A.txt").read()) == "SOS" dummy_file("A.txt", "SOS").run(fun) ## this is same as: #with dummy_file("A.txt", "SOS"): # ok (open("A.txt").read()) == "SOS" * ``spec()`` now checks environment variable $SPEC. This is useful to filter test cases. ## test script from oktest import oktest, run class StrTest(object): def test_upper(self): if spec("returns upper case string"): ok ("sos".upper()) == "SOS" if spec("doesn't change non-alphabetics"): ok ("sos123<>".upper()) == "SOS123<>" if __name__ == "__main__": run() ## terminal $ SPEC="returns upper case string" python test1.py * fix ``oktest.run()`` to print correct traceback if ok() is called from nested function. * fix content of README.txt. Release 0.7.0 ------------- * enhanced to allow users to define custom assertion functions. :: import oktest from oktest import ok # @oktest.assertion def startswith(self, arg): boolean = self.target.startswith(arg) if boolean == self.expected: return True self.failed("%r.startswith(%r) : failed." % (self.target, arg)) # ok ("Sasaki").startswith("Sas") * rename 'ok().hasattr()' to 'ok().has_attr()'. (but old name is also available for backward compatibility.) * change 'chdir()' to take a function as 2nd argument. :: def f(): ... do_something ... chdir('build', f) # The above is same as: with chdir('build'): ... do_something ... * add document of 'oktest.helper.dummy_io()'. :: with dummy_io("SOS") as io: assert sys.stdin.read() == "SOS" print("Haruhi") assert io.stdout == "Haruhi\n" assert io.stderr == "" * fix 'oktest.tracer.Call#__repr__()' to change output according to whether '==' is called or not. This is aimed to make output of 'ok(tr[0]) == [...]' to be more readable. * change 'Runner#run()' to skip AssertionError if it is raised by 'assert ....' and not 'ok() == ...'. Release 0.6.0 ------------- * enhanced to add 'oktest.tracer.Tracer' class. see README for details. * change 'run()' to sort classes order by lineno in source file. * change default argument of 'run()' from '.*Test(Case)$' to '.*(Test|TestCase|_TC)$'. Release 0.5.0 ------------- * change default argument of 'run()' to '.*Test(Case)$'. * enhanced to report untested AssertionObject. * new helper function 'spec()' which describes test specification. * new helper function 'dummy_values()' which changes dictionary temporarily. * new helper function 'dummy_attrs()' which changes object's attributes temporarily. * 'TestCaseRunner' class is renamed to 'TestRunner'. * (undocumented) new helper function 'dummy_environ_vars()'. * (undocumented) new helper function 'using()'. * (uncodumented) add rm_rf() Release 0.4.0 ------------- * enhanced to support 'ok (x).in_delta(y, d)' which raises assertion exception unless y-d < x < y+d. * change test script to support Python 2.7 and 3.x. * fixed several bugs. Release 0.3.0 ------------- * enhanced 'ok (s1) == s2' to display unified diff (diff -u) * changed to call 'before()/after()' instead of 'before_each()/after_each()' (currently 'before_each()/after_each()' is also enabled but they will be disabled in the future) * improved compatibility with unittest module * (internal) 'ValueObject' class is renamed to 'AssertionObject' * (internal) 'Reporter#before_each()' and '#after_each()' are renamed into '#before()' and '#after()' Release 0.2.2 ------------- * enhanced to set 'f.exception' after 'ok (f).raises(Exception)' to get raised exception object * changed to flush output after '.'/'f'/'E' printed * change to get exception message by 'str(ex)' instead of 'ex.message' Release 0.2.1 ------------- * fix 'REAMDE.txt' * fix 'setup.py' Release 0.2.0 ------------- * public release