Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
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
Guerilla InnerSource in enterprises, during the AI hype
onenashev
PRO
0
150
「重なり」は迎える側がつくる ― 人もAIエージェントも歓迎するプロダクトエンジニアリング ―
go0517go
PRO
0
260
プロダクト思考 × 基盤思考を AIで実現する Compound Engineering
tkc66buzz
1
280
Webとヘルスデータ
yukukotani
1
190
Bet AI Day 2026丨AIによって本質に戻るシステムリスク管理
layerx
PRO
0
1.3k
Benchmarking Vector Databases: pgvector vs. LanceDB
tsho
0
150
2026-09-10 【Snowflake World Tour Tokyo 2026】dbt Core と Snowflake で実現する多層的なデータガバナンス / Multi-Layered Data Governance Powered by dbt Core and Snowflake
civitaspo
0
140
コスト最適化の「めんどくさい」を AWS FinOps Agent でチョット楽にする
classmethod_kaz
0
270
Tab5をRubyで動くパソコンにする
kishima
1
250
その指示、Bobにしっかり伝わってる?初心者でも即実践できる プロンプト/コンテキスト設計のコツ
muehara
1
220
Nav2、Nav3 ... はたまた自作?〜 作って理解する Nav3 の設計意図 〜 / Nav2, Nav3 ... or Build Your Own? — Understanding Nav3's design intent by building it from scratch
yanzm
0
260
全員がプロダクトへ向き合う組織を持続成長させるために——組織づくりのフライホイールと4象限 / The Flywheel Model and Four Quadrants for Organizational Design
hiro_torii
3
890
Featured
See All Featured
Self-Hosted WebAssembly Runtime for Runtime-Neutral Checkpoint/Restore in Edge–Cloud Continuum
chikuwait
0
770
SEO for Brand Visibility & Recognition
aleyda
0
4.7k
Why Mistakes Are the Best Teachers: Turning Failure into a Pathway for Growth
auna
0
250
16th Malabo Montpellier Forum Presentation
akademiya2063
PRO
0
370
No one is an island. Learnings from fostering a developers community.
thoeni
21
3.8k
Building Better People: How to give real-time feedback that sticks.
wjessup
370
20k
A brief & incomplete history of UX Design for the World Wide Web: 1989–2019
jct
2
500
Being A Developer After 40
akosma
91
590k
The State of eCommerce SEO: How to Win in Today's Products SERPs - #SEOweek
aleyda
2
11k
Building an army of robots
kneath
306
46k
Let's Do A Bunch of Simple Stuff to Make Websites Faster
chriscoyier
508
140k
Creating an realtime collaboration tool: Agile Flush - .NET Oxford
marcduiker
35
2.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