When you want to test code that depends on something that is too difficult or slow to use in a test environment, swap in a test double for the dependency.
A Dummy passes bogus input values around to satisfy an API.
Item item = new Item(ITEM_NAME);
ShoppingCart cart = new ShoppingCart();
cart.add(item, QUANTITY);
assertEquals(QUANTITY, cart.getItem(ITEM_NAME));
A Stub overrides the real object and returns hard-coded values. Testing with stubs only is state-based testing; you exercise the system and then assert that the system is in an expected state.
ItemPricer pricer = new ItemPricer() {
public BigDecimal getPrice(String name){ return PRICE; }
};
ShoppingCart cart = new ShoppingCart(pricer);
cart.add(dummyItem, QUANTITY);
assertEquals(QUANTITY*PRICE, cart.getCost(ITEM_NAME));
A Mock can return values, but it also cares about the way its methods are called (“strict mocks” care about the order of method calls, whereas “lenient mocks” do not.) Testing with mocks is interaction-based testing; you set expectations on the mock, and the mock verifies the expectations as it is exercised. This example uses JMock to generate the mock (EasyMock is similar):
Mockery context = new Mockery();
final ItemPricer pricer = context.mock(ItemPricer.class);
context.checking(new Expectations() {{
one(pricer).getPrice(ITEM_NAME);
will(returnValue(PRICE));
}});
ShoppingCart cart = new ShoppingCart(pricer);
cart.add(dummyItem, QUANTITY);
cart.getCost(ITEM_NAME);
context.assertIsSatisfied();
A Spy serves the same purpose as a mock: returning values and recording calls to its methods. However, tests with spies are state-based rather than interaction-based, so the tests look more like stub style tests.
TransactionLog log = new TransactionLogSpy();
ShoppingCart cart = new ShoppingCart(log);
cart.add(dummyItem, QUANTITY);
assertEquals(1, logSpy.getNumberOfTransactionsLogged());
assertEquals(QUANTITY*PRICE, log.getTransactionSubTotal(1));
A Fake swaps out a real implementation with a simpler, fake implementation. The classic example is implementing an in-memory database.
Repository
ShoppingCart cart = new ShoppingCart(repo);
cart.add(dummyItem, QUANTITY);
assertEquals(1, repo.getTransactions(cart).Count);
assertEquals(QUANTITY, repo.getById(cart.id()).getQuantity(ITEM_NAME));
While this episode used Java for its examples, all of the above “friends” certainly exist in C++, Python, JavaScript, and probably in YOUR favorite language as well.
Remember to download this episode of Testing on the Toilet and post it in your office.
Permalink | Links to this post |
5 comments:
To help the less test-savvy it would have helped to point out exactly which thing is the stub, fake, etc.
Hi, cool to have a taxonomy of stubs, mocks and all. Question :
* what is 'dummy' about the first object ? Isn't that simply calling the method with a valid argument ?
* the second examples implicitly shows a design decision : the ItemPricer interface must exists at all. This has two nice consequences : first the ShoppingCart codes tends to become trivial (simply calling out methods in dependencies in a nice order) ; second, it becomes possible to concentrate one one implementation of ItemPricer that has all the tricky logics.
My question is : at some point, doesn't the ShoppingCartTest come become much more complicated to write than the logic code ? At wich point does it become intersting to 'forget' some test code that would just do wiring between its dependencies ? Any experience with that ?
* The spy example assumes that ShoppingCart communicates with the outer world, doesn't it ?
On the overall, it is only possible to invite such friends if you code has been prepared in the first place. To keep the analogy, it requires a friend room and some beers in the fridge. How do you tester guys manage to get those design decisions made early enough (and probably by other developpers) so that test code is easy to write ?
Thanks for the ToT series, keep up the fun !
Regards
PH
@Pierre-Henri Trivier: This taxonomy of stubs is also presented in the great "xUnit Test Patterns" book.
希望大家都會非常非常幸福~
「朵朵小語‧優美的眷戀在這個世界上,最重要的一件事,就是好好愛自己。好好愛自己,你的眼睛才能看見天空的美麗,耳朵才能聽見山水的清音。好好愛自己,你才能體會所有美好的東西,所有的文字與音符才能像清泉一樣注入你的心靈。好好愛自己,你才有愛人的能力,也才有讓別人愛上你的魅力。而愛自己的第一步,就是切斷讓自己覺得黏膩的過去,以無沾無滯的輕快心情,大步走向前去。愛自己的第二步,則是隨時保持孩子般的好奇,願意接受未知的指引;也隨時可以拋卻不再需要的行囊,一路雲淡風輕。親愛的,你是天地之間獨一無二的旅人,在陽光與月光的交替之中瀟灑獨行.............................................................................................................有時,你覺得痛。胃痛的時候,接受它,承認這個疼痛是你的身體的一部份,與它和平共處。心痛的時候,接受它,承認這個經驗是你的生命的一部份,與它和平共處。抗拒痛的存在,只會讓它更要證明它的存在,於是你就更痛。所以,.無論你有多麼不喜歡痛的感覺,還是要接納這個痛的事實。與你的痛站在同一邊,不逃避,不閃躲,不再與你的痛爭執,如此,你的痛才會漸漸不再胡鬧,才會乖乖平息下去。.................心願-你許下了一個心願,你閉上眼睛,在冥想之中把這個心願交託宙給宇整個讓宇宙推動它全部的力.量去執行.,你看見星球與星球的引力牽繫著彼此,你聽見虛空與虛空.唱裡著和妙美的聲音,為了你的心願,整個宇宙正在相互傳遞,然後你放下了心願,不僅是放下,最好你還把你的心願忘記,唯有如此,它才能脫離你,發展它自己,
當它在宇宙的遊歷結束之後,它自然會來到你身邊,以你曾經希望的方式回應你,許下,只是讓它發生,放下,才是讓>它實現,你的心願使你懂得不能執著的奧秘...................
Post a Comment