-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
22cb2ff
commit 06b3f20
Showing
1 changed file
with
62 additions
and
137 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,164 +1,89 @@ | ||
### Api overview | ||
- `org.testanza` package | ||
- `Test` - Interface for testing specific behavior for given item. | ||
- [`Case`](#tester-producing-case) - Subclass of `Test` that has name and invokable body. | ||
- [`Suite`](#tester-producing-suite) - Subclass of `Test` that has name and aggregates many tests. | ||
- [`Tester`](#why-use-testanza) - Interface for reusable part of `Test`. | ||
- [`Testers`](#testers) - Contains built-in factories of `Tester`. | ||
- [`TestBuilder`](#tester-producing-suite) - Utility for aggregating many `Test` to build `Suite`. | ||
- [`Junit`](#junit) - Converts testanza `Test` to `junit.framework.Test` | ||
- [`TestanzaAssertionError`](#asserting) - Subclass of `AssertionError`. It's thrown from any built-in `Tester`. | ||
- `TestanzaException` - Thrown in case of incorrect usage of testanza api. | ||
|
||
### Why use Testanza? | ||
|
||
Let's look at test asserting that `ArrayList` implements `Serializable` interface. | ||
|
||
@Test | ||
public void arrayListIsAssignableToSerializable() { | ||
assertTrue(Serializable.class.isAssignableFrom(ArrayList.class)); | ||
} | ||
|
||
To make this test more reusable, we could extract `type` as parameter. | ||
|
||
public void isAssignableToSerializable(Class<?> type) { | ||
assertTrue(Serializable.class.isAssignableFrom(type)); | ||
} | ||
|
||
To make test even more reusable, we could extract `superType` as parameter. | ||
|
||
public void isAssignableTo(Class<?> superType, Class<?> type) { | ||
assertTrue(superType.isAssignableFrom(type)); | ||
} | ||
|
||
In ideal world this would be enough. | ||
We could use *currying* to create function for testing particular types. | ||
|
||
// hypothetical code | ||
isAssignableTo(Serializable.class) | ||
|
||
And apply it to specific type. | ||
### Quick glance | ||
|
||
// hypothetical code | ||
isAssignableTo(Serializable.class)(ArrayList.class) | ||
Quackery contains pre-written test suites for well-known contracts. | ||
If you are implementing your own collection (let's say `MyList`) | ||
just use them instead of writing your own. | ||
|
||
However Java has no *currying*, nor *first-class functions*. | ||
Test test = quacksLike(Collection.class).test(MyList.class); | ||
|
||
To compensate for that Testanza provides `Tester` interface as intermediary. | ||
If you provide Junit3-style `suite()` method, test can be run by junit test runner. | ||
|
||
public interface Tester<T> { | ||
Test test(T item); | ||
@RunWith(AllTests.class) | ||
public class MyListTest { | ||
public static junit.framework.Test suite() { | ||
return junit(quacksLike(Collection.class).test(MyList.class)); | ||
} | ||
} | ||
|
||
Now you can implement it to define reusable part of our example `Test`. | ||
You can combine tests into bigger test suites. | ||
|
||
public static Tester<Class<?>> isAssignableTo(final Class<?> superType) { | ||
... | ||
public static junit.framework.Test suite() { | ||
return junit(newSuite("MyList ... ") | ||
.testThat(MyList.class, quacksLike(Collection.class)) | ||
.testThat(MyList.class, quacksLike( ... )) | ||
.testThat(MyList.class, quacksLike( ... ))); | ||
} | ||
|
||
And apply specific argument to be tested. | ||
|
||
Test test = isAssignableTo(Serializable.class).test(ArrayList.class) | ||
You can define your own contracts by implementing `org.quackery.Tester` interface. | ||
|
||
However this requires a bit of boilerplate code to implement `Tester` in factory-like manner. | ||
|
||
public static Tester<Class<?>> isAssignableTo(final Class<?> superType) { | ||
return new Tester<Class<?>>() { | ||
public Test test(Class<?> type) { | ||
... | ||
} | ||
}; | ||
public interface Tester<T> { | ||
Test test(T item); | ||
} | ||
|
||
### Tester producing Case | ||
|
||
`Tester` can produce a `Case` which is a `Test` testing only single feature. | ||
Full implementation of previous example would look like this | ||
|
||
``` | ||
public static Tester<Class<?>> isAssignableTo(final Class<?> superType) { | ||
return new Tester<Class<?>>() { | ||
public Test test(final Class<?> type) { | ||
String name = type.getSimpleName() + " is assignable to " + superType.getSimpleName(); | ||
Closure body = new Closure() { | ||
public void invoke() { | ||
assertTrue(superType.isAssignableFrom(type)); | ||
`Tester` can produce `Test` being single `Case`. | ||
|
||
public static Tester<Class<?>> quacksLikeFinalClass() { | ||
return new Tester<Class<?>>() { | ||
public Test test(final Class<?> type) { | ||
return new Case(type.getName() + " is final") { | ||
public void run() { | ||
if (!Modifier.isFinal(type.getModifiers())) { | ||
throw new QuackeryAssertionException("" // | ||
+ "\n" // | ||
+ " expected that type\n" // | ||
+ " " + type.getName() + "\n" // | ||
+ " has modifier final\n" // | ||
); | ||
} | ||
} | ||
}; | ||
} | ||
}; | ||
return newCase(name, body); | ||
} | ||
}; | ||
} | ||
``` | ||
|
||
`Case` has useful name and testing `body` which throws `AssertionError` if test fails. | ||
|
||
Now you can create `Test` for `ArrayList` class and any other class you need. | ||
|
||
Test test = isAssignableTo(Serializable.class).test(ArrayList.class) | ||
|
||
Also `org.hamcrest.Matcher` can be converted to `Tester` producing `Case`. | ||
|
||
asTester(hamcrestMatcher); | ||
Or `Test` combine many tests as `Suite`. | ||
|
||
### Tester producing Suite | ||
|
||
`Tester` can represent more abstract contract that is built from more than one smaller `Tester`. | ||
This `Tester` returns test `Suite` that contains list of more basic `Test`. | ||
You can build `Suite` manually or use `TestBuilder`. | ||
|
||
``` | ||
public static Tester<Class<?>> obeysCollectionContract() { | ||
return new Tester<Class<?>>() { | ||
public Test test(Class<?> type) { | ||
TestBuilder builder = new TestBuilder("class " + type.getSimpleName() | ||
+ " obeys Collection contract"); | ||
builder.testThat(type, isAssignableTo(Collection.class)); | ||
builder.testThat(type, hasConstructor(PUBLIC)); | ||
builder.testThat(type, hasConstructor(PUBLIC, Collection.class)); | ||
return builder.build(); | ||
public static Tester<Class<?>> quacksLikeX() { | ||
return new Tester<Class<?>>() { | ||
public Test test(Class<?> type) { | ||
return newSuite(type + " quacks like X") | ||
.testThat(type, quacksLikeA()) | ||
.testThat(type, quacksLikeB()); | ||
} | ||
}; | ||
} | ||
}; | ||
} | ||
``` | ||
|
||
`TestBuilder` is overloaded to accept collections of items instead of single one (`testThatAll`). | ||
It can also take `org.hamcrest.Matcher` and turn it into simple `Tester` under the hood. | ||
|
||
### Asserting | ||
|
||
Failure of `Test` is signaled by throwing `AssertionError`. | ||
In previous examples `org.junit.Assert` was used, but testanza is agnostic about which assertion library you use. | ||
Built-in `Testers` throws `TestanzaAssertionError` (extending `AssertionError`). | ||
Consider copying that behavior when implementing your own `Tester`. | ||
|
||
### Junit | ||
|
||
Testanza `Test` can be converted to `junit.framework.Test` and run by junit test runner. | ||
Quackery is agnostic about what assertion library you use and what exceptions/errors you throw. | ||
However all built-in testers throw `org.quackery.QuackeryAssertionException` in case of failed tests | ||
and `org.quackery.QuackeryAssumptionException` in case when test cannot be run. | ||
You are encouraged to copy that behavior. | ||
|
||
``` | ||
@RunWith(AllTests.class) | ||
public class MyListTest { | ||
public static junit.framework.Test suite() { | ||
TestBuilder builder = new TestBuilder("class MyList obeys contracts"); | ||
builder.testThat(MyList.class, obeysCollectionContract()); | ||
builder.testThat(MyList.class, obeysListContract()); | ||
builder.testThat(MyList.class, isAssignableTo(Serializable.class)); | ||
Test test = builder.build(); | ||
return junit(test); | ||
} | ||
} | ||
``` | ||
Those exceptions are translated to native exceptions/errors when converting to other frameworks (like junit). | ||
|
||
This will create a hierarchy of test for your class. | ||
|
||
![MyListTest.png](MyListTest.png "MyListTest.png") | ||
### Junit | ||
|
||
### Testers | ||
Quackery `Test` can be converted to `junit.framework.Test` and run by junit test runner. | ||
|
||
Testanza has some built-in factories of `Tester`. | ||
@RunWith(AllTests.class) | ||
public class MyListTest { | ||
public static junit.framework.Test suite() { | ||
return junit(quacksLike(Collection.class).test(MyList.class)); | ||
} | ||
} | ||
|
||
- `asTester(Matcher)` - Converts hamcrest `Matcher` to `Tester`. | ||
- `hasConstructor` - tests if `Class` has `Constructor` with given parameters and access level. | ||
- `hasModifier`/`hasNoModifier` - tests if `Constructor`, `Method`, `Field` or `Class` has (or has not) given modifier. | ||
|
||
Exceptions throws by quackery are translated to junit natives: | ||
- `org.quackery.QuackeryAssertionException` to `java.lang.AssertionError` | ||
- `org.quackery.QuackeryAssumptionException` to `org.junit.internal.AssumptionViolatedException` |