5.12.15 QA Lab: тестирование программного обеспечения.
Upcoming events: goo.gl/I2gJ4H
Доклад о Play-Swagger, проекте с открытым исходным кодом, разрабатываемом в Zalando с использованием Scala и Play Framework. О том, как использование API First и Swagger позволяет ускорить процесс разработки, упростить взаимодействие команд и повысить качество продукта.
2. You need two days to write test cases!?
You already have the Requirements Specification.
Just copy and paste from it.
http://inderpsingh.blogspot.de/2010/05/funny-things-that-testers-hear.html
10. REST
• Client-Server
• Stateless
• Cacheable
• Layered System
• Uniform Interface
• Identification of resources
• Manipulation of resources through these representations
• Self-descriptive messages
• Hypermedia as the engine of application state
20. something used or associated with and usually contrasted
with hardware as the entire set of programs, procedures,
and related documentation associated with a system and
especially a computer system.
Full
21. Can you remember quality metrics of your current project?
Libraries it uses?
Runtime environment it runs in?
Container or/and hypervisor?
Operating system?
Firmware/BIOS ?
22. [A] constructive approach to the problem of program
correctness [is] a usual technique to make a program and
then to test it. But, program testing can be a very effective
way to show the presence of bugs, it is hopelessly
inadequate for showing their absence. The only effective
way to raise the confidence level of a program significantly
is to give a convincing proof of its correctness.
-- Edsger, Wybe Di jkstra, ACM Turing Lecture, The Humble Programmer, 1972
24. Types are specifications of possible values complying to that Type
Types describe the rules that values must comply to
A possibility to generate ranges of data values for given Types
25. Boolean: True or False
Equality: Equal or Not Equal
Ordering: Greater, Equal or Less
Provable
33. DRY
Most people take DRY to mean you shouldn't duplicate code.
That's not its intention. The idea behind DRY is far grander
than that. DRY says that every piece of system knowledge
should have one authoritative, unambiguous representation.
Dave Thomas
38. • Easy to use
• Human readable
• Widest adoption
• Open Source
• Scala and Java
• Dynamic recompilation
• Hot reload
• Asynchronous IO
• Easy to use
45. Metadata
swagger: "2.0"
info:
version: 1.0.0
title: Swagger Petstore
description: A sample API that uses a petstore as an example to
demonstrate features in the swagger-2.0 specification
termsOfService: http://swagger.io/terms/
contact:
name: Swagger API Team
email: foo@example.com
url: http://madskristensen.net
license:
name: MIT
url: http://github.com/gruntjs/grunt/blob/master/LICENSE-MIT
host: petstore.swagger.io
basePath: /api
schemes:
- http
consumes:
- application/json
produces:
- application/json
47. Test data
definitions:
Pet:
allOf:
- $ref: '#/definitions/NewPet'
- required:
- id
properties:
id:
type: integer
format: int64
NewPet:
required:
- name
properties:
name:
type: string
tag:
type: string
Error:
required:
- code
- message
properties:
code:
type: integer
format: int32
message:
type: string
object generatorDefinitions {
def createPet = _generate(PetGenerator)
def createNewPet = _generate(NewPetGenerator)
def createError = _generate(ErrorGenerator)
// test data generator for /definitions/Pet
val PetGenerator =
for {
id <- Gen.option(arbitrary[Long])
name <- arbitrary[String]
tag <- Gen.option(arbitrary[String])
} yield Pet(id, name, tag)
// test data generator for /definitions/NewPet
val NewPetGenerator =
for {
name <- arbitrary[String]
tag <- Gen.option(arbitrary[String])
} yield NewPet(name, tag)
// test data generator for /definitions/Error
val ErrorGenerator =
for {
code <- arbitrary[Int]
message <- arbitrary[String]
} yield Error(code, message)
def _generate[T](gen: Gen[T]) = (count: Int) =>
for (i <- 1 to count) yield gen.sample
}
48. Validations'#/definitions/NewPet'
red:
rties:
ype: integer
ormat: int64
s:
string
string
ge
s:
integer
t: int32
:
string
class PetValidation(instance: Pet) {
import de.zalando.play.controllers.PlayValidations._
val allValidations = Seq.empty[scala.Either[scala.Seq[ParsingError], String]]
val result = {
val errors = allValidations.filter(_.isLeft).flatMap(_.left.get)
if (errors.nonEmpty) Left(errors) else Right(instance)
}
}
class NewPetValidation(instance: NewPet) {
import de.zalando.play.controllers.PlayValidations._
val allValidations = Seq.empty[scala.Either[scala.Seq[ParsingError], String]]
val result = {
val errors = allValidations.filter(_.isLeft).flatMap(_.left.get)
if (errors.nonEmpty) Left(errors) else Right(instance)
}
}
class ErrorValidation(instance: Error) {
import de.zalando.play.controllers.PlayValidations._
val allValidations = Seq.empty[scala.Either[scala.Seq[ParsingError], String]]
val result = {
val errors = allValidations.filter(_.isLeft).flatMap(_.left.get)
if (errors.nonEmpty) Left(errors) else Right(instance)
}
}
49. Validations
/pets/{id}:
get:
description: Returns a user based
on a single ID, if the user does not
have access to the pet
operationId: find pet by id
parameters:
- name: id
in: path
description: ID of pet to fetch
required: true
type: integer
format: int64
responses:
200:
description: pet response
schema:
$ref: '#/definitions/Pet'
default:
description: unexpected error
schema:
$ref: '#/definitions/Error'
class ValidationForPetexpandedYamlfindPetById(in: (Long)) {
val (id) = in
val idConstraints = new ValidationBase[Long] {
override def constraints: Seq[Constraint[Long]] = Seq()
}
val normalValidations =
Seq(idConstraints.applyConstraints(id))
val containerValidations =
Seq.empty[scala.Either[scala.Seq[ParsingError], String]]
val rightResult = Right((id))
val allValidations = normalValidations ++
containerValidations
val result = {
val errors =
allValidations.filter(_.isLeft).flatMap(_.left.get)
if (errors.nonEmpty) Left(errors) else rightResult
}
}
50. Tests
"discard invalid data" in new WithApplication {
val genInputs =
for {
id <- arbitrary[Long]
} yield (id)
val inputs = genInputs suchThat { i => new ValidationForPetexpandedYamlfindPetById(i).result !=
Right(i) }
val props = forAll(inputs) { i => testInvalidInput(i) }
checkResult(props)
}
51. Tests
def testInvalidInput(in: (Long)) = {
val (id) = in
val url = s"""/api/pets/${id}"""
val path = route(FakeRequest(GET, url)).get
val validation = new ValidationForPetexpandedYamlfindPetById(id).result
lazy val validations = validation.left.get flatMap {
_.messages map { m => contentAsString(path).contains(m) ?= true }
}
("given an URL: [" + url + "]") |: all(
status(path) ?= BAD_REQUEST,
contentType(path) ?= Some("application/json"),
validation.isLeft ?= true,
all(validations:_*)
)
}
52. Controllers
private val findPetByIdResponseMimeType = "application/json"
private val findPetByIdActionSuccessStatus = Status(200)
private type findPetByIdActionRequestType = (Long)
private type findPetByIdActionResultType = Pet
private type findPetByIdActionType = findPetByIdActionRequestType => Either[Throwable, findPetByIdActionResultType]
private def errorToStatusfindPetById: PartialFunction[Throwable, Status] = PartialFunction.empty[Throwable, Status]
def findPetByIdAction = (f: findPetByIdActionType) => (id: Long) => Action {
val result = new ValidationForPetexpandedYamlfindPetById(id).result.right.map {
processValidfindPetByIdRequest(f)
}
implicit val marshaller = parsingErrors2Writable(findPetByIdResponseMimeType)
val response = result.left.map { BadRequest(_) }
response.fold(a => a, c => c)
}
private def processValidfindPetByIdRequest(f: findPetByIdActionType)(request: findPetByIdActionRequestType) = {
val callerResult = f(request)
val status = callerResult match {
case Left(error) => (errorToStatusfindPetById orElse defaultErrorMapping)(error)
case Right(result) => findPetByIdActionSuccessStatus
}
implicit val findPetByIdWritableJson = anyToWritable[findPetByIdActionResultType](findPetByIdResponseMimeType)
status(callerResult)
}
53. Skeletons
class PetexpandedYaml extends PetexpandedYamlBase {
// handler for GET /pets
def findPets = findPetsAction { in : (Option[Seq[String]], Option[Int]) =>
val (tags, limit) = in
???
}
// handler for POST /pets
def addPet = addPetAction { in : (NewPet) =>
val (pet) = in
???
}
// handler for GET /pets/{id}
def findPetById = findPetByIdAction { in : (Long) =>
val (id) = in
???
}
// handler for DELETE /pets/{id}
def deletePet = deletePetAction { in : (Long) =>
val (id) = in
???
}
}