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
130
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
460
Supercharge your CI with pipelines
rosomack
0
75
Other Decks in Technology
See All in Technology
公式ドキュメントの歩き方etc
coco_se
1
120
しぶいSRE: サーバから見えない障害にどう向き合うか。ラストワンマイルのデバッグ実践 / Shibui SRE
kanny
13
6.4k
見守りエージェントを作ってみた(ローカルLLM + Hermes Agent)
happysamurai294
0
110
DMM.com 購入改善推進チーム におけるCodeRabbitを用いた レビューフロー改善の一例
ysknsid25
2
660
なぜ私たちのSREプラクティスはなかなか機能しないのか 〜システムより先に組織を見る〜 / Why our SRE practices aren't really working
vtryo
4
4k
凡エンジニアがこの先生きのこるためには。〜TypeScript完全に理解したい〜
alchemy1115
2
310
第67回コンピュータビジョン勉強会CVPR2026読会前編
tsukamotokenji
0
140
LLMやAIエージェントをソフトウェアに組み込むプラクティス
shibuiwilliam
2
410
CSに"SLO"は要らない、経営層に"99.9%"は伝わらない - SREを全社に"翻訳"する3原則
cscengineer
PRO
1
5k
Oracle Base Database Service 技術詳細
oracle4engineer
PRO
15
110k
Terraform共通モジュールをチーム横断で“変えられる”運用へ ― リリースと適用の分離
kekke_n
1
3.3k
壊して学ぶAWS CDK: そのcdk deployで消えるもの、残るもの
k_adachi_01
1
380
Featured
See All Featured
Skip the Path - Find Your Career Trail
mkilby
1
170
Thoughts on Productivity
jonyablonski
76
5.2k
Building AI with AI
inesmontani
PRO
1
1.1k
Optimising Largest Contentful Paint
csswizardry
37
3.8k
sira's awesome portfolio website redesign presentation
elsirapls
0
300
The Cost Of JavaScript in 2023
addyosmani
55
10k
Rebuilding a faster, lazier Slack
samanthasiow
85
9.6k
Distributed Sagas: A Protocol for Coordinating Microservices
caitiem20
333
23k
End of SEO as We Know It (SMX Advanced Version)
ipullrank
3
4.3k
The SEO identity crisis: Don't let AI make you average
varn
0
510
WENDY [Excerpt]
tessaabrams
11
38k
16th Malabo Montpellier Forum Presentation
akademiya2063
PRO
0
240
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