Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
How to make your unit tests Spektacular
Search
Mikolaj Leszczynski
February 21, 2018
Technology
140
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
How to make your unit tests Spektacular
Mikolaj Leszczynski
February 21, 2018
More Decks by Mikolaj Leszczynski
See All by Mikolaj Leszczynski
Bye bye RxJava: Building flexible SDKs with Kotlin and Coroutines
rosomack
4
480
Supercharge your CI with pipelines
rosomack
0
77
Other Decks in Technology
See All in Technology
"優秀な誰か"の下位互換にならないために
yut0naga1_fa
1
1.8k
SAENM ~Jupyterで残すPoCの試行錯誤、未来の自分と仲間を助ける記録!~
terapyon
0
110
スキマバイトという雇用形態は存在しないので、雇用期間1日のアルバイト契約を事業者とワーカーが結ぶ手続きを支えるのがタイミーなんです
akitoshiga
0
200
10年後、Engineering 行為を剥奪されるとして、わたしたちは、どう、何を創ればいいだろうか。 / If Engineering Is Taken Away in 10 Years, How and What Should We Create?
bitkey
PRO
1
190
Temporal 最高!でも改めて「日付」について調べたら人類の負債すぎる件
riyaamemiya
0
150
実体験から学ぶ分析エージェントの開発と運⽤
recruitengineers
PRO
8
2.4k
Metadata - Section 1: HTML Basics
mohab1
0
120
AIカンパニーへの転換を支えた、エンジニアの仕事の再定義
kworkdev
PRO
2
1.2k
Local CI × GitHub Actions
gogutan
2
190
TinyGoでオブザーバビリティ?OpenTelemetryはTinyGoで使えるのか / Is OpenTelemetry available in TinyGo environment?
ymotongpoo
2
140
QA4AI 2026-Oct 生成AI時代の品質保証
tokuty
1
860
[JAWS-UG彩の国埼玉#12]そろそろ準備する?re:Invent2026をやさしく解説 / reinvent2026-sainokuni
sh_fk2
1
340
Featured
See All Featured
The Art of Programming - Codeland 2020
erikaheidi
57
14k
Lightning Talk: Beautiful Slides for Beginners
inesmontani
PRO
2
730
Imperfection Machines: The Place of Print at Facebook
scottboms
270
14k
Visual Storytelling: How to be a Superhuman Communicator
reverentgeek
2
710
New Earth Scene 8
popppiees
4
2.7k
We Analyzed 250 Million AI Search Results: Here's What I Found
joshbly
2
2k
Evolving SEO for Evolving Search Engines
ryanjones
0
330
Building Better People: How to give real-time feedback that sticks.
wjessup
370
20k
The Illustrated Guide to Node.js - THAT Conference 2024
reverentgeek
1
570
The Psychology of Web Performance [Beyond Tellerrand 2023]
tammyeverts
49
3.6k
The untapped power of vector embeddings
frankvandijk
2
1.9k
Lessons Learnt from Crawling 1000+ Websites
charlesmeaden
PRO
1
1.6k
Transcript
How to make your unit tests Spektacular
Spek
Spek
Spek
Hello JUnit my old friend
Our JUnit test structure @Test fun test_name() { //given set
up context here (mocks, variables, prerequisites etc.) //when execute action here //then assertions go here }
Testing circles of hell 1. Naming hell 2. Context hell
3. Assertion hell
Naming Hell @Test public void validateFacebookAccessTokenAndLogin_CallsFacebookTokenRequestPermissionsDeniedError_WhenAllNecessaryPermissionsAre NotGranted() { . .
. }
@Test public void should_not_enable_pay_button_when_promotion_is_applied_and_there_is_nothing_to_pay_and_credit_card_is_required() { // given givenPaymentCardWidgetInitialised(); givenPaymentDetailsWidgetInitialised(); givenNoCardSelected();
paymentDetailsPresenter.takeView(mockView); int discountPercentage = 100; boolean creditCardRequired = true; . . . // when . . . // then . . . } Context Hell
Context Hell @Test public void should_not_enable_pay_button_when_promotion_is_applied_and_there_is_nothing_to_pay_and_credit_card_is_required_and_the_ payment_card_widget_is_initialised_and_the_payment_details_widget_is_initialised_and_no_card_is_selected() { // given
givenPaymentCardWidgetInitialised(); givenPaymentDetailsWidgetInitialised(); givenNoCardSelected(); paymentDetailsPresenter.takeView(mockView); int discountPercentage = 100; boolean creditCardRequired = true; . . . // when . . . // then . . . }
Context Hell @Test public void should_not_enable_pay_button_when_promotion_is_applied_and_there_is_nothing_to_pay_and_credit_card_is_required() { // given givenPaymentCardWidgetInitialised();
givenPaymentDetailsWidgetInitialised(); givenNoCardSelected(); paymentDetailsPresenter.takeView(mockView); int discountPercentage = 100; boolean creditCardRequired = true; PaymentPlan paymentPlan = createPaymentPlanBuilder().build(); Promotion promotion = createPromotionBuilder() .setCreditCardRequired(creditCardRequired) .setDiscountPercentage(discountPercentage) .setPaymentPlan(paymentPlan) .build(); Observable<Promotion> promoCodeObservable = Observable.just(promotion); // when paymentDetailsPresenter.onPromoCodeInitialised(promoCodeObservable); // then verify(mockView, never()).enablePayButton(); }
Context Hell @Test public void should_not_enable_pay_button() { // when paymentDetailsPresenter.onPromoCodeInitialised(promoCodeObservable);
// then verify(mockView, never()).enablePayButton(); }
Assertion Hell @Test public void should_return_specialists() { // given DoctorModel
doctorModel = getDoctorModel(); when(mockDoctorsService.getAllSpecialists()).thenReturn(Single.just(Collections.singletonList(doctorModel))); // when TestObserver<List<DoctorSimple>> assertableSubscriber = retrofitDoctorsGateway.getDoctorsOfType(DoctorType.create(DoctorType.Type.SPECIALIST)).test(); // then assertableSubscriber.assertComplete(); List<List<DoctorSimple>> onNextEvents = assertableSubscriber.values(); assertThat(onNextEvents.size()).isEqualTo(1); DoctorSimple doctorSimple = onNextEvents.get(0).get(0); assertThat(doctorSimple.getDoctorType()).isEqualTo(DoctorType.create(DoctorType.Type.SPECIALIST)); assertThat(doctorSimple.getId()).isEqualTo(String.valueOf(doctorModel.getId())); assertThat(doctorSimple.getName()).isEqualTo(doctorModel.getName()); assertThat(doctorSimple.getAvatarUrl()).isEqualTo(doctorModel.getAvatar()); assertAll(); assertableSubscriber.assertNoErrors(); }
Testing circles of hell 1. Naming hell 2. Context hell
3. Assertion hell
Execution flow hierarchy Execution flow: @Before public void setUp() {}
@Test public void test1() {} setUp —> test1 @Test public void test2() {} setUp —> test2 @Test public void test3() {} setUp —> test3 @Test public void test4() {} setUp —> test4
Spek tastic
BDD • Verbose in communication • Concise in code •
Putting tests in context • Creating a specification for the test subject • Documenting the subject’s behaviour
Spek syntax given("a patient") { // set up context here
on("treating the patient") { // action goes here it("heals the patient") { // assertion (test) goes here } } }
Spek syntax xxx("description") { // BLOCK BODY }
Spek syntax given("a patient") { // set up context here
on("treating the patient") { // action goes here it("heals the patient") { // assertion (test) goes here } } }
Spek syntax given("a patient") { // set up context here
on("treating the patient") { // action goes here it("heals the patient") { // assertion (test) goes here } } }
Spek syntax given("a patient") { // set up context here
on("treating the patient") { // action goes here it("heals the patient") { // assertion (test) goes here } } }
It it("tests the universe”) { true `should equal to` true
}
On fun compute() = 6 * 7 on("running the ultimate
computation") { val actualAnswer = compute() it("should equal 42") { actualAnswer `should be` 42 } it("should be divisible by 2") { actualAnswer % 2 `should be` 0 } }
Given given("the number 6") { val x = 6 given("the
number 7") { val y = 7 on(“multiplying") { val actualAnswer = multiply(x, y) it("should equal 42") { actualAnswer `should be` 42 } } } }
given("the number 2") { val x = 2 on("squaring") {
val actualAnswer = squared(x) it("should equal 4") { actualAnswer `should be` 4 } } on(“cubing") { val actualAnswer = cubed(x) it("should equal 8") { actualAnswer `should be` 8 } } }
State in tests val subject = SubjectUnderTest() it("checks the subject
instance") { println("Instance: $subject") } it("checks the subject instance again") { println("Instance again: $subject") } Output Instance: randoms.SubjectUnderTest@3bb9a3ff Instance again: randoms.SubjectUnderTest@3bb9a3ff
Memoized val subject by memoized { SubjectUnderTest() } it("checks the
subject instance") { println("Instance: $subject") } it("checks the subject instance again") { println("Instance again: $subject") } Output Instance: randoms.SubjectUnderTest@59309333 Instance again: randoms.SubjectUnderTest@222545dc
val subject by memoized { SubjectUnderTest() } on("action 1") {
println("On instance 1: $subject") it("checks the subject instance") { println("It instance 1: $subject") } } on("action 2") { println("On instance 2: $subject") it("checks the subject instance again") { println("It instance 2: $subject") } } Output On instance 1: randoms.SubjectUnderTest@67d48005 It instance 1: randoms.SubjectUnderTest@67d48005 On instance 2: randoms.SubjectUnderTest@478190fc It instance 2: randoms.SubjectUnderTest@478190fc
val subject by memoized { SubjectUnderTest() } given("context 1") {
println("Given instance 1: $subject") it("checks the subject instance") { println("It instance 1: $subject") } } given("context 2") { println("Given instance 2: $subject") it("checks the subject instance again") { println("It instance 2: $subject") } } Output Given instance 1: randoms.SubjectUnderTest@6a28ffa4 Given instance 2: randoms.SubjectUnderTest@6a28ffa4 It instance 1: randoms.SubjectUnderTest@6a28ffa4 It instance 2: randoms.SubjectUnderTest@222545dc
Due to how Spek is structured, group scopes are eagerly
evaluated during the discovery phase. Any logic that needs to be evaluated before and/or after test scopes should be done using fixtures (…)
val subject by memoized { SubjectUnderTest() } given("context 1") {
beforeEachTest { println("Given instance 1: $subject") } it("checks the subject instance") { println("It instance 1: $subject") } } given("context 2") { beforeEachTest { println("Given instance 2: $subject") } it("checks the subject instance again") { println("It instance 2: $subject") } } Output Given instance 1: randoms.SubjectUnderTest@895e367 It instance 1: randoms.SubjectUnderTest@895e367 Given instance 2: randoms.SubjectUnderTest@2b72cb8a It instance 2: randoms.SubjectUnderTest@2b72cb8a
Test execution order 1. Discovery - all given blocks are
executed first 2. Execution: For each it: 1. beforeEachTest sections of containing givens 2. Containing on is executed 3. it is executed
Inside a given: 1. To instantiate variables, always use memoized
2. Everything else should be in beforeEachTest
Our rules for well written speks 1. Describe the behaviour,
not the implementation 2. One assertion per it 3. One action per on 4. One statement per given (ideally!) 5. Everything in a given should be in beforeEachTest or use memoized
class CreatePasswordValidatorSpek : Spek({ val mockContext by memoized { mock<Context>()
} val createPasswordValidator by memoized { CreatePasswordValidator(mockContext) } val INVALID_PASSWORD = "nonvalidpassword" val PATIENT_ID = "patient_id" val ERROR_MESSAGE = "message" given("an error message is returned") { beforeEachTest { whenever(mockContext.getString(any())).thenReturn(ERROR_MESSAGE) } given("a non valid password request") { val createPasswordRequest by memoized { CreatePasswordRequest.builder() .setPassword(INVALID_PASSWORD) .setPatientId(PATIENT_ID) .build() } on("validation") { val testObserver = createPasswordValidator.validate(createPasswordRequest).test() it("returns a validation exception") { testObserver.assertError(InvalidPasswordException::class.java) } it("sets the error message to \"$ERROR_MESSAGE\"") { testObserver.errors()[0].message `should be` ERROR_MESSAGE } } } } })
None
Spek drawbacks
Great tools that allow focusing on testing behaviour instead of
implementation
So much easier to read the tests
Allows to create clean, concise and expressive tests
I love how you can create a test skeleton for
the whole class, and then write all the mocks and verifications in it
The test outputs are just plain english and you can
go straight to the error when it fails
Tests have structure that allows to easily check which scenarios
are covered and which are lacking tests
I’m amazed at how well Spek tests scale compared to
traditional tests
Questions? • https://github.com/Rosomack/SpekExamples • http://spekframework.org/docs/latest/#_overview • https://github.com/spekframework/spek • http://hadihariri.com/2012/04/11/what-bdd-has-taught-me/ •
https://github.com/mannodermaus/android-junit5
We’re hiring! Presented by Mikolaj Leszczynski Say hi on Twitter
& Medium! : @TheAngroid