Introduction
Hamcrest is a framework for writing matcher objects allowing 'match' rules to be defined declaratively. There are a number of situations where matchers are invaluble, such as UI validation, or data filtering, but it is in the area of writing flexible tests that matchers are most commonly used. This tutorial shows you how to use Hamcrest for unit testing.
Hamcrest는 'match'rule을 선언적으로 정의 해 matcher objects를 작성하기위한 framework입니다. UI 유효성 UI validation 또는 데이터 필터링과 같이 matchers가 사용할 수 없는 상황도 많이 있지만 matcher가 가장 일반적으로 사용되는 곳은 flexible tests를 작성하는 영역에 있습니다. 이 tutorial에서는 unit testing를 위해 Hamcrest를 사용하는 방법을 보여줍니다.
When writing tests it is sometimes difficult to get the balance right between overspecifying the test (and making it brittle to changes), and not specifying enough (making the test less valuable since it continues to pass even when the thing being tested is broken). Having a tool that allows you to pick out precisely the aspect under test and describe the values it should have, to a controlled level of precision, helps greatly in writing tests that are "just right". Such tests fail when the behaviour of the aspect under test deviates from the expected behaviour, yet continue to pass when minor, unrelated changes to the behaviour are made.
테스트를 작성할 때 테스트를 지나치게 자세하게 지정하는 것 (그리고 수정이 어렵게 하는 것)과 충분한 테스트를 하지 않는 것 사이의 균형을 잡기가 어렵기 때문에 (이럴 경우 테스트 대상에 문제가 생긴 경우에도 테스트가 pass 될 수 있어 해당 테스트가 유효하지 않게 됩니다.) 테스트 할 aspect를 정확하게 선택할 수 있도록 하고, 가져야 할 값을 describe 해서 정밀하게 제여할 수 있도록 하는 도구를 사용하면 "just right" 테스트를 작성하는 데 크게 도움이됩니다. 이러한 테스트는 테스트중인 aspect의 동작이 예상되는 벗어나는 경우 fail 하고 , minor 한 변화나 테스트 범위에서 벗어난 변경이 있는 경우 pass 해야 합니다.
My first Hamcrest test
We'll start by writing a very simple JUnit 3 test, but instead of using JUnit's assertEquals
methods, we use Hamcrest's assertThat
construct and the standard set of matchers, both of which we statically import:
아래 간단한 JUnit 3 test를 작성할 겁니다. JUnit의 assertEquals methods를 사용하는 대신에 Hamcrest의 assertThat construct와 matchers의 표준 세트를 사용할 겁니다. 이 둘은 정적으로 import 합니다.
``` import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*;
import junit.framework.TestCase;
public class BiscuitTest extends TestCase { public void testEquals() { Biscuit theBiscuit = new Biscuit("Ginger"); Biscuit myBiscuit = new Biscuit("Ginger"); assertThat(theBiscuit, equalTo(myBiscuit)); } } ```
The assertThat
method is a stylized sentence for making a test assertion. In this example, the subject of the assertion is the object biscuit
that is the first method parameter. The second method parameter is a matcher for Biscuit
objects, here a matcher that checks one object is equal to another using the Object equals
method. The test passes since the Biscuit
class defines an equals method.
assertThat method는 test assertion을 만들기 위한 양식화 된 문장입니다. 이 예에서는 assertion의 subject는 첫 번째 method 매개 변수 인 object biscuit입니다. 두 번째 method 매개 변수는 Biscuit objects에 대한 matcher입니다. 여기에서 한 object를 검사하는 matcher는 Object equals method를 사용하는것과 같습니다. 이 테스트는 Biscuit 클래스가 equals method를 정의하기 때문에 pass 하게 됩니다.
If you have more than one assertion in your test you can include an identifier for the tested value in the assertion:
하나 이상의 assertion이 있는 경우 테스트 된 값에 대한 identifier를 assertion에 포함 할 수 있습니다.
assertThat("chocolate chips", theBiscuit.getChocolateChipCount(), equalTo(10)); assertThat("hazelnuts", theBiscuit.getHazelnutCount(), equalTo(3));
Other test frameworks
Hamcrest has been designed from the outset to integrate with different unit testing frameworks. For example, Hamcrest can be used with JUnit 3 and 4 and TestNG. (For details have a look at the examples that come with the full Hamcrest distribution.) It is easy enough to migrate to using Hamcrest-style assertions in an existing test suite, since other assertion styles can co-exist with Hamcrest's.
Hamcrest는 처음부터 다른 unit testing framework와 통합되도록 설계되었습니다. 예를 들어, Hamcrest는 JUnit 3과 4 및 TestNG와 함께 사용할 수 있습니다. (자세한 내용은 전체 Hamcrest distribution과 함께 제공되는 예제를 참조하십시오.) 다른 assertion 스타일이 Hamcrest와 같이 사용될 수 있기 때문에 기존 test suite에서 Hamcrest-style assertions를 사용하는 것으로 마이그레이션하는 것은 쉽습니다.
Hamcrest can also be used with mock objects frameworks by using adaptors to bridge from the mock objects framework's concept of a matcher to a Hamcrest matcher. For example, JMock 1's constraints are Hamcrest's matchers. Hamcrest provides a JMock 1 adaptor to allow you to use Hamcrest matchers in your JMock 1 tests. JMock 2 doesn't need such an adaptor layer since it is designed to use Hamcrest as its matching library. Hamcrest also provides adaptors for EasyMock 2. Again, see the Hamcrest examples for more details.
Hamcrest는 또한 mock objects framework와 같이 사용 될 수 있습니다. 그러려면 mock objects framework의 matcher와 Hamcrest의 matcher 에 대한 concept을 연결해 주도록 adaptors를 사용하면 됩니다. 예를 들어, JMock 1의 constraints는 Hamcrest의 matchers입니다. Hamcrest는 JMock 1 테스트에서 Hamcrest matchers를 사용할 수 있도록 JMock 1 어댑터를 제공합니다. JMock 2는 Hamcrest를 매칭 라이브러리로 사용하기 때문에 그러한 adaptor layer가 필요하지 않습니다. Hamcrest는 EasyMock 2 용 어댑터도 제공합니다. 자세한 내용은 Hamcrest 예제를 참조하십시오.
A tour of common matchers
Hamcrest comes with a library of useful matchers. Here are some of the most important ones.
Hamcrest는 유용한 matchers 라이브러리를 제공합니다. 그 중에 중요한 것들 입니다.
Coreanything
- always matches, useful if you don't care what the object under test isdescribedAs
- decorator to adding custom failure descriptionis
- decorator to improve readability - see "Sugar", below
- Logical
allOf
- matches if all matchers match, short circuits (like Java &&)anyOf
- matches if any matchers match, short circuits (like Java ||)not
- matches if the wrapped matcher doesn't match and vice versa
- Object
equalTo
- test object equality using Object.equalshasToString
- test Object.toStringinstanceOf
, isCompatibleType
- test typenotNullValue
, nullValue
- test for nullsameInstance
- test object identity
- Beans
hasProperty
- test JavaBeans properties
- Collections
array
- test an array's elements against an array of matchershasEntry
, hasKey
, hasValue
- test a map contains an entry, key or valuehasItem
, hasItems
- test a collection contains elementshasItemInArray
- test an array contains an element
- Number
closeTo
- test floating point values are close to a given valuegreaterThan
, greaterThanOrEqualTo
, lessThan
, lessThanOrEqualTo
- test ordering
- Text
equalToIgnoringCase
- test string equality ignoring caseequalToIgnoringWhiteSpace
- test string equality ignoring differences in runs of whitespacecontainsString
, endsWith
, startsWith
- test string matching
Sugar
Hamcrest strives to make your tests as readable as possible. For example, the is
matcher is a wrapper that doesn't add any extra behavior to the underlying matcher. The following assertions are all equivalent:
Hamcrest는 여러분이 최대한 readable한 테스트를 만들 수 있도록 노력합니다. 예를 들어, is matcher는 기본이되는 matcher에 추가의 동작을 추가하지 않는 wrapper 입니다. (어떤 동작을 하지는 않고 단지 가독성 -readable- 을 제공하기 위한 것).
아래 assertion 들은 다 똑 같습니다.
assertThat(theBiscuit, equalTo(myBiscuit)); assertThat(theBiscuit, is(equalTo(myBiscuit))); assertThat(theBiscuit, is(myBiscuit));
The last form is allowed since is(T value)
is overloaded to return is(equalTo(value))
.
마지막 form은 is(T value)가 is(equalTo (value))를 return하도록 overload 되어 있기 때문에 가능합니다.
Writing custom matchers
Hamcrest comes bundled with lots of useful matchers, but you'll probably find that you need to create your own from time to time to fit your testing needs. This commonly occurs when you find a fragment of code that tests the same set of properties over and over again (and in different tests), and you want to bundle the fragment into a single assertion. By writing your own matcher you'll eliminate code duplication and make your tests more readable!
Hamcrest는 많은 유용한 matchers와 함께 번들로 제공되지만, 테스트 요구에 맞추기 위해 수시로 자신 만의 템플릿을 만들어야 할 필요가있을 것입니다. 이것은 일반적으로 동일한 properties set을 반복적으로 테스트 하는 (다른 테스트 들에서도) code의 fragment를 발견 했을 때 그리고 이 fragment를 단일 assertion으로 묶으려는 경우에 해당 될 겁니다. 자신 만의 matcher를 작성함으로써 코드 중복을 제거하고 테스트의 가독성을 훨씬 높일 수 있습니다!
Let's write our own matcher for testing if a double value has the value NaN (not a number). This is the test we want to write:
double 값의 값이 NaN (숫자가 아님) 인 경우를 테스트하기 위해 자체 matcher를 작성해 보겠습니다. 아래 그 예제가 있습니다.
public void testSquareRootOfMinusOneIsNotANumber() { assertThat(Math.sqrt(-1), is(notANumber())); }
And here's the implementation:
``` package org.hamcrest.examples.tutorial;
import org.hamcrest.Description; import org.hamcrest.Factory; import org.hamcrest.Matcher; import org.hamcrest.TypeSafeMatcher;
public class IsNotANumber extends TypeSafeMatcher {
@Override public boolean matchesSafely(Double number) { return number.isNaN(); }
public void describeTo(Description description) { description.appendText("not a number"); }
@Factory public static Matcher notANumber() { return new IsNotANumber(); }
} ```
The assertThat
method is a generic method which takes a Matcher
parameterized by the type of the subject of the assertion. We are asserting things about Double
values, so we know that we need a Matcher<Double>
. For our Matcher implementation it is most convenient to subclass TypeSafeMatcher
, which does the cast to a Double
for us. We need only implement the matchesSafely
method - which simply checks to see if the Double
is NaN - and the describeTo
method - which is used to produce a failure message when a test fails. Here's an example of how the failure message looks:
assertThat method는, assertion의 subject의 type에 의해 파라미터 화 된 Matcher를 취하는 generic method입니다. 우리는 Double 값에 대해 asserting하고 있으므로 Matcher <Double>이 필요하다는 것을 알고 있습니다. 이 Matcher를 구현하는데 있어 TypeSafeMatcher를 서브 클래스로 implement 하는 것이 가장 좋습니다. TypeSafeMatcher는 Double로 형변환을 하게 됩니다. matchesSafely method와 describeTo method만 implement 하면 됩니다. matchesSafely method는 단순히 Double이 NaN인지 확인하고 describeTo method 테스트가 fail 할 경우 오류 메시지를 생성하는 데 사용됩니다. 다음은 failure message가 표시되는 예입니다.
assertThat(1.0, is(notANumber()));
fails with the message
java.lang.AssertionError: Expected: is not a number got : <1.0>
The third method in our matcher is a convenience factory method. We statically import this method to use the matcher in our test:
우리의 matcher에서 세 번째 method는 편리한 factory method 입니다. test에서 matcher를 사용하기 위해 이 method를 정적(statically)으로 가져옵니다.
``` import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*;
import static org.hamcrest.examples.tutorial.IsNotANumber.notANumber;
import junit.framework.TestCase;
public class NumberTest extends TestCase {
public void testSquareRootOfMinusOneIsNotANumber() { assertThat(Math.sqrt(-1), is(notANumber())); } } ```
Even though the notANumber
method creates a new matcher each time it is called, you should not assume this is the only usage pattern for your matcher. Therefore you should make sure your matcher is stateless, so a single instance can be reused between matches.
비록 notANumber method가 호출 될 때마다 new matcher를 생성하지만, 이것이 matcher의 유일한 사용 patter 이라고 예단 해서는 안됩니다. 여러분은 이 matcher가 stateless 임을 유념해야 합니다. single instance 가 matcher들 사이에서 재사용 될 수 있습니다.
Sugar generation
If you produce more than a few custom matchers it becomes annoying to have to import them all individually. It would be nice to be able to group them together in a single class, so they can be imported using a single static import much like the Hamcrest library matchers. Hamcrest helps out here by providing a way to do this by using a generator.
여러가지 custom matchers를 만들었다면 이들을 각각 import 하는 일이 성가실 겁니다. 하나의 클래스에서 이들을 그룹화 하면 좋겠죠. 그렇게 하면 Hamcrest 라이브러리 matchers와 같이 single static import를 사용하여 한꺼번에 import 할 수 있을 겁니다. Hamcrest는 generator를 사용하여 이를 수행 할 수있는 방법을 제공합니다.
First, create an XML configuration file listing all the Matcher classes that should be searched for factory methods annotated with the org.hamcrest.Factory annotation. For example:
먼저, 모든 Matcher classes을 리스팅 한 XML configuration file을 생성합니다. 이것은 org.hamcrest.Factory annotation과 함께 factory methods annotated를 하기 위해 검색 될 겁니다. 예 :
```
```
Second, run the org.hamcrest.generator.config.XmlConfigurator
command-line tool that comes with Hamcrest. This tool takes the XML configuration file and generates a single Java class that includes all the factory methods specified by the XML file. Running it with no arguments will display a usage message. Here's the output for the example.
둘째, Hamcrest와 함께 제공되는 org.hamcrest.generator.config.XmlConfigurator command-line tool를 실행하십시오. 이 도구는 XML 구성 파일을 사용하여 XML 파일에 지정된 모든 팩토리 method를 포함하는 단일 Java 클래스를 생성합니다. 인수없이 실행하면 사용법 메시지가 표시됩니다. 이 예제의 결과는 다음과 같습니다.
``` // Generated source. package org.hamcrest.examples.tutorial;
public class Matchers {
public static org.hamcrest.Matcher is(T param1) { return org.hamcrest.core.Is.is(param1); }
public static org.hamcrest.Matcher is(java.lang.Class param1) { return org.hamcrest.core.Is.is(param1); }
public static org.hamcrest.Matcher is(org.hamcrest.Matcher param1) { return org.hamcrest.core.Is.is(param1); }
public static org.hamcrest.Matcher notANumber() { return org.hamcrest.examples.tutorial.IsNotANumber.notANumber(); }
} ```
Finally, we can update our test to use the new Matchers
class.
마지막으로 new Matchers class를 사용하도록 아래처럼 업데이트 합니다.
``` import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.examples.tutorial.Matchers.*;
import junit.framework.TestCase;
public class CustomSugarNumberTest extends TestCase {
public void testSquareRootOfMinusOneIsNotANumber() { assertThat(Math.sqrt(-1), is(notANumber())); } } ```
Notice we are now using the Hamcrest library is
matcher imported from our own custom Matchers
class.
Notice 우리는 custom Matchers class 에서 importe된 matcher 인 Hamcrest library를 사용하게 됩니다.
Where next?
See FurtherResources.
--Tom White