Spock

Testy z fabułą


Magda Stożek
http://softwaremill.com/
http://jug.zgora.pl/

Dlaczego Spock?

  • czytelny i zwięzły
  • niski próg wejścia
  • wygodny
  • pasuje do Javy

class JugMeetingSpec extends Specification {

  def jugMeeting = new Meeting(["Spock", "Gerrit"])
  def attendee = new Person("Mścibór")

  def "should make Mścibór's life better"() {
	given:
	attendee.setKnownFrameworks(["JUnit", "Mockito"])
	attendee.setMoodPercent(20)
   
	when:
	attendee.attend(jugMeeting)

	then:
	attendee.moodPercent > 80
	attendee.knownFrameworks.contains("Spock")
	attendee.isAwake
  }
} 

Składniki specyfikacji

class JugMeetingSpec extends Specification {

  def jugMeeting = new Meeting(["Spock", "Gerrit"])
  def attendee = new Person("Mścibór")

  @Shared
  def pub = new Pub("Stara Komenda")	

  def setup() { ... }
  def cleanup() { ... }
  def setupSpec() { ... }
  def cleanupSpec() { ... }
} 

Bloki

  • given/setup
  • when
  • then
  • cleanup

  • expect
  • where

Co nam daje zepsuty test

Condition not satisfied:

sms.content == "Hello Mom\nI need money quickly\nThanks and CU later"
|   |       |
|   |       false
|   |       6 differences (88% similarity)
|   |       Hello Mom\nI need money quickly\nThanks and CU(------)
|   |       Hello Mom\nI need money quickly\nThanks and CU( later)
|   Hello Mom
|   I need money quickly
|   Thanks and CU
SmsEmail[content=Hello Mom\nI need money quickly\nThanks and CU,recipients=[123123123],
sender=,username=user@gmail.com,password=secret,replyTo=mailto:replyTo@gmail.com]
		

Piękno Grooviego

  • kod Javy jest zazwyczaj poprawnym kodem Grooviego
  • nie wymaga średników
  • nie wymaga podwójnego typowania
  • def name = new ComplicatedLongClassName()
  • == oznacza equals
  • wbudowane wsparcie dla kolekcji
  • def list = ["one", "two"]
  • świetna dokumentacja oparta na przykładach

Zasilanie testów danymi

Życie przed Spockiem - @DataProvider z TestNG

@DataProvider
private Object[][] testData() {
	return new Object[][] {
		new Object[]{"", "", "", "", MISSING_REQUIRED_PARAMETER},
		new Object[]{"invalid id", "", "", "", USER_DOES_NOT_EXIST},
		new Object[]{testUserId, "", "invalid", "", INVALID_PARAMETER_VALUE},
		new Object[]{notMyUser, "", "", "", NO_RIGHTS_TO_CUSTOMER}
	};
}

Zasilanie testów danymi

Jak to się robi w Spocku

@Unroll
def "Should return #expectedStatus for #userId, #email, #phoneNumber"() 
  when:
  def response = updateUserByRest(userId, email, phoneNumber)

  then:
  response == expectedStatus.toString()

  where:
  userId       | email     | phoneNumber || expectedStatus
  ""           | ""        | ""          || MISSING_REQUIRED_PARAMETER
  "invalid id" | ""        | ""          || USER_DOES_NOT_EXIST
  testUserId   | ""        | "invalid"   || INVALID_PARAMETER_VALUE
  notMyUser    | ""        | ""          || NO_RIGHTS_TO_CUSTOMER
}

Zasilanie testów danymi

Wynik dzięki @Unroll

Sprawdzanie wyjątków

Życie przed Spockiem:
  • @Test(expected = IllegalArgumentException.class)
  • try-fail-catch

@Test
public void testForExceptions() {
    try{
        zoo.acceptAnimal(cat);

        fail("expected IllegalArgumentException");

    } catch(IllegalArgumentException e){
        //ignore, this exception is expected.
    }

Sprawdzanie wyjątków

A w Spocku:

when:
zoo.acceptAnimal(cat)

then:
def e = thrown(IllegalArgumentException)
e.message.contains("wild animals only")
		

Interakcje - mocki i stuby

def userDao = Mock(UserDao)
def userCache = Mock(UserCache)
def app = new Application(userDao, userCache)

def "should use cache instead of dao"() {
  when:
  app.findUser("John")

  then:
  1 * userCache.find("John")
  0 * userDao.find(_)
} 

def "should return user"() {
  given:
  userDao.getSkills("John") >> ["Java", "Groovy", "Spock"]

  when:
  def john = app.findUser("John")

  then:
  john.skills.contains("Spock")	
} 

Mocki

Sprawdzanie wywołań, liczebności, parametrów, ...

(_..3) * zoo.receive("zebra")
1 * zoo./re.*/("surprise")
(1.._) * zoo.receive(_) 
2 * zoo.receive(_ as Bird) 
1 * zoo.receive(!null) 
0 * zoo.receive({ it.age() > 3 })
1 * process.invoke("ls", "-a", _, !null, { ["abcd"].contains(it) })
  

Pytania?

when:
presentation.finish()

then:
attendee.isAwake
attendee.wantsToTrySpock
attendee.questions > 0
	   

Slajdy: http://magdzikk.github.io/spock-slides/

/