You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

322 lines
12 KiB

4 years ago
  1. PyHamcrest
  2. ==========
  3. | |docs| |travis| |coveralls| |landscape| |scrutinizer| |codeclimate|
  4. | |version| |downloads| |wheel| |supported-versions| |supported-implementations|
  5. .. |docs| image:: https://readthedocs.org/projects/pyhamcrest/badge/?style=flat
  6. :target: https://pyhamcrest.readthedocs.org/
  7. :alt: Documentation Status
  8. .. |travis| image:: http://img.shields.io/travis/hamcrest/PyHamcrest/master.png?style=flat
  9. :alt: Travis-CI Build Status
  10. :target: https://travis-ci.org/hamcrest/PyHamcrest
  11. .. |appveyor| image:: https://ci.appveyor.com/api/projects/status/github/hamcrest/PyHamcrest?branch=master
  12. :alt: AppVeyor Build Status
  13. :target: https://ci.appveyor.com/project/hamcrest/PyHamcrest
  14. .. |coveralls| image:: http://img.shields.io/coveralls/hamcrest/PyHamcrest/master.png?style=flat
  15. :alt: Coverage Status
  16. :target: https://coveralls.io/r/hamcrest/PyHamcrest
  17. .. |landscape| image:: https://landscape.io/github/hamcrest/PyHamcrest/master/landscape.svg?style=flat
  18. :target: https://landscape.io/github/hamcrest/PyHamcrest/master
  19. :alt: Code Quality Status
  20. .. |codeclimate| image:: https://codeclimate.com/github/hamcrest/PyHamcrest/badges/gpa.svg
  21. :target: https://codeclimate.com/github/hamcrest/PyHamcrest
  22. :alt: Code Climate
  23. .. |version| image:: http://img.shields.io/pypi/v/PyHamcrest.png?style=flat
  24. :alt: PyPI Package latest release
  25. :target: https://pypi.python.org/pypi/PyHamcrest
  26. .. |downloads| image:: http://img.shields.io/pypi/dm/PyHamcrest.png?style=flat
  27. :alt: PyPI Package monthly downloads
  28. :target: https://pypi.python.org/pypi/PyHamcrest
  29. .. |wheel| image:: https://pypip.in/wheel/PyHamcrest/badge.png?style=flat
  30. :alt: PyPI Wheel
  31. :target: https://pypi.python.org/pypi/PyHamcrest
  32. .. |supported-versions| image:: https://pypip.in/py_versions/PyHamcrest/badge.png?style=flat
  33. :alt: Supported versions
  34. :target: https://pypi.python.org/pypi/PyHamcrest
  35. .. |supported-implementations| image:: https://pypip.in/implementation/PyHamcrest/badge.png?style=flat
  36. :alt: Supported imlementations
  37. :target: https://pypi.python.org/pypi/PyHamcrest
  38. .. |scrutinizer| image:: https://img.shields.io/scrutinizer/g/hamcrest/PyHamcrest/master.png?style=flat
  39. :alt: Scrtinizer Status
  40. :target: https://scrutinizer-ci.com/g/hamcrest/PyHamcrest/
  41. Introduction
  42. ============
  43. PyHamcrest is a framework for writing matcher objects, allowing you to
  44. declaratively define "match" rules. There are a number of situations where
  45. matchers are invaluable, such as UI validation, or data filtering, but it is in
  46. the area of writing flexible tests that matchers are most commonly used. This
  47. tutorial shows you how to use PyHamcrest for unit testing.
  48. When writing tests it is sometimes difficult to get the balance right between
  49. overspecifying the test (and making it brittle to changes), and not specifying
  50. enough (making the test less valuable since it continues to pass even when the
  51. thing being tested is broken). Having a tool that allows you to pick out
  52. precisely the aspect under test and describe the values it should have, to a
  53. controlled level of precision, helps greatly in writing tests that are "just
  54. right." Such tests fail when the behavior of the aspect under test deviates
  55. from the expected behavior, yet continue to pass when minor, unrelated changes
  56. to the behaviour are made.
  57. Installation
  58. ============
  59. Hamcrest can be installed using the usual Python packaging tools. It depends on
  60. distribute, but as long as you have a network connection when you install, the
  61. installation process will take care of that for you.
  62. My first PyHamcrest test
  63. ========================
  64. We'll start by writing a very simple PyUnit test, but instead of using PyUnit's
  65. ``assertEqual`` method, we'll use PyHamcrest's ``assert_that`` construct and
  66. the standard set of matchers:
  67. .. code:: python
  68. from hamcrest import *
  69. import unittest
  70. class BiscuitTest(unittest.TestCase):
  71. def testEquals(self):
  72. theBiscuit = Biscuit('Ginger')
  73. myBiscuit = Biscuit('Ginger')
  74. assert_that(theBiscuit, equal_to(myBiscuit))
  75. if __name__ == '__main__':
  76. unittest.main()
  77. The ``assert_that`` function is a stylized sentence for making a test
  78. assertion. In this example, the subject of the assertion is the object
  79. ``theBiscuit``, which is the first method parameter. The second method
  80. parameter is a matcher for ``Biscuit`` objects, here a matcher that checks one
  81. object is equal to another using the Python ``==`` operator. The test passes
  82. since the ``Biscuit`` class defines an ``__eq__`` method.
  83. If you have more than one assertion in your test you can include an identifier
  84. for the tested value in the assertion:
  85. .. code:: python
  86. assert_that(theBiscuit.getChocolateChipCount(), equal_to(10), 'chocolate chips')
  87. assert_that(theBiscuit.getHazelnutCount(), equal_to(3), 'hazelnuts')
  88. As a convenience, assert_that can also be used to verify a boolean condition:
  89. .. code:: python
  90. assert_that(theBiscuit.isCooked(), 'cooked')
  91. This is equivalent to the ``assert_`` method of unittest.TestCase, but because
  92. it's a standalone function, it offers greater flexibility in test writing.
  93. Predefined matchers
  94. ===================
  95. PyHamcrest comes with a library of useful matchers:
  96. * Object
  97. * ``equal_to`` - match equal object
  98. * ``has_length`` - match ``len()``
  99. * ``has_property`` - match value of property with given name
  100. * ``has_properties`` - match an object that has all of the given properties.
  101. * ``has_string`` - match ``str()``
  102. * ``instance_of`` - match object type
  103. * ``none``, ``not_none`` - match ``None``, or not ``None``
  104. * ``same_instance`` - match same object
  105. * ``calling, raises`` - wrap a method call and assert that it raises an exception
  106. * Number
  107. * ``close_to`` - match number close to a given value
  108. * ``greater_than``, ``greater_than_or_equal_to``, ``less_than``,
  109. ``less_than_or_equal_to`` - match numeric ordering
  110. * Text
  111. * ``contains_string`` - match part of a string
  112. * ``ends_with`` - match the end of a string
  113. * ``equal_to_ignoring_case`` - match the complete string but ignore case
  114. * ``equal_to_ignoring_whitespace`` - match the complete string but ignore extra whitespace
  115. * ``matches_regexp`` - match a regular expression in a string
  116. * ``starts_with`` - match the beginning of a string
  117. * ``string_contains_in_order`` - match parts of a string, in relative order
  118. * Logical
  119. * ``all_of`` - ``and`` together all matchers
  120. * ``any_of`` - ``or`` together all matchers
  121. * ``anything`` - match anything, useful in composite matchers when you don't care about a particular value
  122. * ``is_not`` - negate the matcher
  123. * Sequence
  124. * ``contains`` - exactly match the entire sequence
  125. * ``contains_inanyorder`` - match the entire sequence, but in any order
  126. * ``has_item`` - match if given item appears in the sequence
  127. * ``has_items`` - match if all given items appear in the sequence, in any order
  128. * ``is_in`` - match if item appears in the given sequence
  129. * ``only_contains`` - match if sequence's items appear in given list
  130. * ``empty`` - match if the sequence is empty
  131. * Dictionary
  132. * ``has_entries`` - match dictionary with list of key-value pairs
  133. * ``has_entry`` - match dictionary containing a key-value pair
  134. * ``has_key`` - match dictionary with a key
  135. * ``has_value`` - match dictionary with a value
  136. * Decorator
  137. * ``calling`` - wrap a callable in a deffered object, for subsequent matching on calling behaviour
  138. * ``raises`` - Ensure that a deferred callable raises as expected
  139. * ``described_as`` - give the matcher a custom failure description
  140. * ``is_`` - decorator to improve readability - see `Syntactic sugar` below
  141. The arguments for many of these matchers accept not just a matching value, but
  142. another matcher, so matchers can be composed for greater flexibility. For
  143. example, ``only_contains(less_than(5))`` will match any sequence where every
  144. item is less than 5.
  145. Syntactic sugar
  146. ===============
  147. PyHamcrest strives to make your tests as readable as possible. For example, the
  148. ``is_`` matcher is a wrapper that doesn't add any extra behavior to the
  149. underlying matcher. The following assertions are all equivalent:
  150. .. code:: python
  151. assert_that(theBiscuit, equal_to(myBiscuit))
  152. assert_that(theBiscuit, is_(equal_to(myBiscuit)))
  153. assert_that(theBiscuit, is_(myBiscuit))
  154. The last form is allowed since ``is_(value)`` wraps most non-matcher arguments
  155. with ``equal_to``. But if the argument is a type, it is wrapped with
  156. ``instance_of``, so the following are also equivalent:
  157. .. code:: python
  158. assert_that(theBiscuit, instance_of(Biscuit))
  159. assert_that(theBiscuit, is_(instance_of(Biscuit)))
  160. assert_that(theBiscuit, is_(Biscuit))
  161. *Note that PyHamcrest's ``is_`` matcher is unrelated to Python's ``is``
  162. operator. The matcher for object identity is ``same_instance``.*
  163. Writing custom matchers
  164. =======================
  165. PyHamcrest comes bundled with lots of useful matchers, but you'll probably find
  166. that you need to create your own from time to time to fit your testing needs.
  167. This commonly occurs when you find a fragment of code that tests the same set
  168. of properties over and over again (and in different tests), and you want to
  169. bundle the fragment into a single assertion. By writing your own matcher you'll
  170. eliminate code duplication and make your tests more readable!
  171. Let's write our own matcher for testing if a calendar date falls on a Saturday.
  172. This is the test we want to write:
  173. .. code:: python
  174. def testDateIsOnASaturday(self):
  175. d = datetime.date(2008, 04, 26)
  176. assert_that(d, is_(on_a_saturday()))
  177. And here's the implementation:
  178. .. code:: python
  179. from hamcrest.core.base_matcher import BaseMatcher
  180. from hamcrest.core.helpers.hasmethod import hasmethod
  181. class IsGivenDayOfWeek(BaseMatcher):
  182. def __init__(self, day):
  183. self.day = day # Monday is 0, Sunday is 6
  184. def _matches(self, item):
  185. if not hasmethod(item, 'weekday'):
  186. return False
  187. return item.weekday() == self.day
  188. def describe_to(self, description):
  189. day_as_string = ['Monday', 'Tuesday', 'Wednesday', 'Thursday',
  190. 'Friday', 'Saturday', 'Sunday']
  191. description.append_text('calendar date falling on ') \
  192. .append_text(day_as_string[self.day])
  193. def on_a_saturday():
  194. return IsGivenDayOfWeek(5)
  195. For our Matcher implementation we implement the ``_matches`` method - which
  196. calls the ``weekday`` method after confirming that the argument (which may not
  197. be a date) has such a method - and the ``describe_to`` method - which is used
  198. to produce a failure message when a test fails. Here's an example of how the
  199. failure message looks:
  200. .. code:: python
  201. assert_that(datetime.date(2008, 04, 06), is_(on_a_saturday()))
  202. fails with the message::
  203. AssertionError:
  204. Expected: is calendar date falling on Saturday
  205. got: <2008-04-06>
  206. Let's say this matcher is saved in a module named ``isgivendayofweek``. We
  207. could use it in our test by importing the factory function ``on_a_saturday``:
  208. .. code:: python
  209. from hamcrest import *
  210. import unittest
  211. from isgivendayofweek import on_a_saturday
  212. class DateTest(unittest.TestCase):
  213. def testDateIsOnASaturday(self):
  214. d = datetime.date(2008, 04, 26)
  215. assert_that(d, is_(on_a_saturday()))
  216. if __name__ == '__main__':
  217. unittest.main()
  218. Even though the ``on_a_saturday`` function creates a new matcher each time it
  219. is called, you should not assume this is the only usage pattern for your
  220. matcher. Therefore you should make sure your matcher is stateless, so a single
  221. instance can be reused between matches.
  222. More resources
  223. ==============
  224. * Documentation_
  225. * Package_
  226. * Sources_
  227. * Hamcrest_
  228. .. _Documentation: http://readthedocs.org/docs/pyhamcrest/en/V1.8.2/
  229. .. _Package: http://pypi.python.org/pypi/PyHamcrest
  230. .. _Sources: https://github.com/hamcrest/PyHamcrest
  231. .. _Hamcrest: http://hamcrest.org