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
79
Other Decks in Technology
See All in Technology
Claude Codeで作ったアプリを安全に公開する/Built with Claude Japan 2026
yoshidashingo
0
210
AWSを利用する上で知っておきたい名前解決の話 2026
nagisa53
5
3.3k
AWS Quick 入門 | 目指せ Quick マスター 01
inariku
PRO
2
240
生成AI時代のIAMのマニアックな話
takuros
6
2.8k
会社説明資料_最新版
demaecan
0
32k
分析作業をどこまで任せるか自分で決められる AIネイティブBI『Golden Analytics』が 良さげな話 /golden-analytics-is-ai-native-bi
shinyaa31
0
130
公共システムAdventCalendar2026
toru_kubota
0
290
AI時代の組織作り:チーム開発から「個人×AI」への移行と、持続可能な「開発資本」の築き方
ham0215
2
420
HQ駆動開発で最高のAI-Contextを作ろう
sansantech
PRO
1
120
基本から分かる!AWSセキュリティ運用の考え方と任せ方
cmusudakeisuke
1
460
In-Context Learning再入門。ICLの勾配降下的解釈の研究動向を追ってみる。
dai08srhg
1
750
Agent Harnessはどう作る?事例から学ぶAI Agentの設計思想
sergicalsix
0
220
Featured
See All Featured
Agile Actions for Facilitating Distributed Teams - ADO2019
mkilby
0
340
Leveraging Curiosity to Care for An Aging Population
cassininazir
1
560
Marketing Yourself as an Engineer | Alaka | Gurzu
gurzu
0
370
The Curse of the Amulet
leimatthew05
3
17k
The Web Performance Landscape in 2024 [PerfNow 2024]
tammyeverts
12
1.4k
Winning Ecommerce Organic Search in an AI Era - #searchnstuff2025
aleyda
2
2.2k
The browser strikes back
jonoalderson
0
1.9k
RailsConf 2023
tenderlove
30
1.6k
Six Lessons from altMBA
skipperchong
29
4.6k
Chasing Engaging Ingredients in Design
codingconduct
0
390
A Tale of Four Properties
chriscoyier
163
24k
What’s in a name? Adding method to the madness
productmarketing
PRO
24
4.2k
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