Post on 24-Jan-2017
Ewolucja modelu danych w testach funkcjonalnych -
case studySławomir Kluz
Architektura- mikroserwisy- rest/json- event procesory- zewnętrzni dostawcy- aplikacje klienckie- CI/CD- ~ 40 serwerów
microservice 01
microservice 03
microservice 02
microservice 05
microservice 06
microservice 04
microservice 07
postgresql apache kafka couchbase redis
microservice 08
website backoffice
proxy
Testy● API: spójność danych, szybki feedback, wpływ na integracje + CD● website/backoffice - selenium● testy wydajnościwe API
Wersja 1.0● start projektu - testy API● gatling jako framework testowy (testy funkcjonalne + wydajnościowe)● model danych
○ tekst/mapy
object Account {
val fullBody = """{ "password": "${password}", "email": "${email}", "nickName": "${nickName}", "birthDate": "${birthDate}", "phone": "${phone}", "street": "${street}" }"""
val updateBody = """{ "id":${accountId}, "password": "${password}", "email": "${email}", "nickName": "${nickName}", "birthDate": "${birthDate}", "phone": "${phone}", "street": "${street}" "revision": ${accountRevision} }"""
val incompleteBodyMissingEmail = """{ "password": "${password}", "nickName": "${nickName}", "birthDate": "${birthDate}", "phone": "${phone}", "street": "${street}" }"""}
Wersja 1.0● start projektu - testy API● gatling jako framework testowy (testy funkcjonalne + wydajnościowe)● model danych
○ tekst/mapy○ json wrapper
object ParticipantDataFeeder {
private val dataString = """{ |"name":"Arsenal", |"sport": { | "id" : "football", | "name" : "Football" |}, |"metadata":{ | "name":"Arsenal", | "type":"team" | } |}""".stripMargin
def correctData = dataString
def updateWithoutMetadata = new jsonBody(dataString).remove("metadata").get
def withName(name: String) = new jsonBody(dataString).set("name", name).get
def withSport(sport: String) = new jsonBody(dataString).setJson("sport", sport).get
def withoutMetadata = new jsonBody(dataString).remove("metadata").get
}
private val testScenario: PopulatedScenarioBuilder = scenario(getClass.getSimpleName) .exec(Session.rootAuthenticationToken(Some(testUniverse)) .exec(Participant.put("spin","star",UUID.randomUUID().toString, ParticipantDataFeeder.withoutMetadata, status.in(200,201), jsonPath("$.metadata.name").notExists, jsonPath("$.metadata.type").notExists )) ).inject(performanceStrategy)
Wersja 1.0● zalety
○ szybkość tworzenia○ możliwość użycia w innych narzędziach○ zrozumiałe dla nietechnicznej osoby
● problemy○ ciężkie do utrzymania przy zmieniającym się modelu, powiązaniu danych i dużej ilości testów○ ciężkie do użycia poza frameworkiem testowym
Nowe wymagania● powstają aplikacje webowe
○ test są dość wolne - problem przy CI/CD○ nie wszystkie dane da się stworzyć z użyciem strony internetowej○ użycie API do przygotowania danych
● narzędzia○ potrzeba narzędzi do analizy danych
● problemy z frameworkiem testowym○ dość wolne wdrożenie○ brak wsparcia IDE
Wersja 2.0● struktura kodu projektu - sbt
Wersja 2.0● zmiana frameworka testowego● tests-model
○ proste klasy opisujące model danych request/response nie związane z żadnym frameworkiem testowym
○ zależności pomiędzy obiektami○ samplery: przykładowe obiekty w danym stanie
case class ChatMessage( `type`: String, content: MessageContent, sender: Option[AccountReference] = None, tags: Option[Array[String]] = None, sentAt: Option[String] = None)
case class AccountReference(id: Int, `type`: String, name: String)
case class MessageContent(text: String)
object PaymentMethodSampler {
def creditCardPayment = PaymentMethod( provider = Some("realex"), `type` = Some("credit-card") )
def invalidProviderPayment = PaymentMethod( provider = Some("polcard"), `type` = Some("credit-card") )
Wersja 2.0● tests-api
○ serializacja/deserializacja obiektów modelu○ wbudowany mechanizm z rest-assured ○ warstwa “serwisów”
object ChatService {
val _sendMessage = " /chats/{universe}/{chatType}/{chatId}/messages"
def sendMessage(session: Session, universe: String, chatType: String, chatId: String, message: AnyRef) = { given() .specification(Specifications.authorizedExternal(session)) .pathParam("universe", universe) .pathParam("chatType", chatType) .pathParam("chatId", chatId) .body(message) .when() .post(_sendMessage) .Then() .assertThat() .statusCode(200) }}
def chatWithHistory(session: Session, universe: String, chatType: String, chatId: String): ChatHistory = { given() .specification(Specifications.authorizedExternal(session)) .pathParam("universe", universe) .pathParam("chatType", chatType) .pathParam("chatId", chatId) .when() .get(_chatWithHistory) .Then() .assertThat() .statusCode(200) .extract().body().as(classOf[ChatHistory]) }
var history = ChatService.chatWithHistory(staffSession, universeId, customerAccount.`type`.get, chatId)
assert(history.id == chatId, "ChatHistory id should be equal customerId") assert(history.messages.size == 5, "Five messages should be created") assert(history.participants.size == 2, "Two participants should be included in chat")
inside (history.messages.head) { case ChatMessage(_type, content, sender, tags, sentAt) => _type should be ("something") sentAt.get should startWith("prefix") tags should contain("one", "two") }
Wersja 2.0● tests-web
○ możliwość użycia warstwy serwisów do przygotowania danych○ możliwość sprawdzenia stanu danych (eventy)○ asercje z użyciem obiektów modelu
class DisplayEventTest extends BaseSuite {
behavior of "Events page"
it should "display event with proper data" taggedAs Smoke in { val (operatorEvent, operatorMarket) = EventProvider.createStartFootballMatchWinnerEvent() Backoffice.open().login() val tradingPage = Backoffice.openTradingPage() val eventPage = tradingPage.openEventPage(operatorEvent.id.get) operatorEvent.name shouldBe eventPage.eventName.getText operatorEvent.id.get shouldBe eventPage.eventId.getText operatorEvent.display.get shouldBe eventPage.isEventShow() operatorEvent.active.get shouldBe eventPage.isEventActive() operatorEvent.timeSettings.get.startTime.substring(0,10) shouldBe eventPage.startTime.getText.substring(0,10) }
}
Wersja 2.0● zalety
○ łatwość wprowadzania zmian/utrzymania kodu○ czytelność kodu○ szybkie testy przeglądarkowe○ tworzenie zewnętrznych narzędzi z użyciem serwisów○ sterowanie mechanizmem serializacji○ wsparcie IDE
● wady○ użycie danych niezgodnych z modelem○ problemy z serializacją
Pytania