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
470
Supercharge your CI with pipelines
rosomack
0
77
Other Decks in Technology
See All in Technology
Von-Neumann Machines, Dreams & The Futures of IT
ufried
0
130
10年欲しかった音楽管理アプリを、AIと一緒に作りはじめた
judau
1
130
メルカリにおけるAI時代の高速プロトタイピング基盤「Arca」
ryotarai
15
11k
なぜ「決定性」が 決定的に重要なのか? Durable Execution 基盤の数理的理解 #serverlessjp / ServerlessDays Tokyo 2026
ytaka23
4
1.3k
HacobuにおけるFDEとは/登壇資料(戸井田 裕貴)
hacobu
PRO
1
110
Datadog で始める トークンセーフティなAI 導⼊と定着
vkbaba
1
110
spanner-autoscalerに学ぶ CRD設計パターン 〜自動化と緊急時対応を両立する Kubernetesコントローラーの作り方〜
tkuchiki
0
160
品質と信頼性を地続きにする
grimoh
2
1.1k
What the customer really needed
kawaguti
PRO
3
230
リアーキテクチャ後の障害ゼロを目指したShadow Testingの取り組み
nihonbuson
PRO
1
180
Oracle Base Database Service 技術詳細
oracle4engineer
PRO
16
120k
「大丈夫そう?」をObservabilityで確かめる
mrmtsu
0
180
Featured
See All Featured
Taking LLMs out of the black box: A practical guide to human-in-the-loop distillation
inesmontani
PRO
3
2.4k
Optimizing for Happiness
mojombo
378
71k
Max Prin - Stacking Signals: How International SEO Comes Together (And Falls Apart)
techseoconnect
PRO
0
470
Breaking role norms: Why Content Design is so much more than writing copy - Taylor Woolridge
uxyall
1
420
Abbi's Birthday
coloredviolet
4
10k
ReactJS: Keep Simple. Everything can be a component!
pedronauck
666
130k
Kristin Tynski - Automating Marketing Tasks With AI
techseoconnect
PRO
0
530
Tell your own story through comics
letsgokoyo
1
1.1k
Introduction to Domain-Driven Design and Collaborative software design
baasie
1
1k
SEOcharity - Dark patterns in SEO and UX: How to avoid them and build a more ethical web
sarafernandez
0
290
First, design no harm
axbom
PRO
2
1.3k
Chrome DevTools: State of the Union 2024 - Debugging React & Beyond
addyosmani
10
1.4k
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