Skip to content
This repository has been archived by the owner on Mar 12, 2020. It is now read-only.

Commit

Permalink
Add some functionality from the legacy code, increase coverage #7 #8
Browse files Browse the repository at this point in the history
  • Loading branch information
tdauth committed Nov 13, 2018
1 parent f46c22d commit 770af5b
Show file tree
Hide file tree
Showing 10 changed files with 105 additions and 28 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ Basically, it does only provide only the two calls `getState` and `executeAction
* `sbt scalastyle` to make a check with ScalaStyle.
* `sbt doc` to generate the scaladoc API documentation.
* `sbt scalafmt` to format the Scala source files with scalafmt.
* `sbt release with-defaults` to create a release with a new version number which is published to a repository.

## NFA for the Representation of Tests
A nondeterministic finite automaton represents the states of the GUI during the test.
Expand Down
2 changes: 1 addition & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ mainClass in (Compile, run) := Some("de.retest.guistatemachine.rest.WebServer")
mainClass in (Compile, packageBin) := Some("de.retest.guistatemachine.rest.WebServer")

publishTo := {
val nexus = "https://my.artifact.repo.net/"
val nexus = "https://oss.sonatype.org/"
if (isSnapshot.value)
Some("snapshots" at nexus + "content/repositories/snapshots")
else
Expand Down
2 changes: 1 addition & 1 deletion src/main/scala/de/retest/guistatemachine/api/Action.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package de.retest.guistatemachine.api

/**
* Interaction from the user with the GUI.
* TODO #6 Use an abstract representation of actions from retest-model instead of Selenium.
* TODO #6 Use an abstract representation of actions from retest-model instead of Selenium. The legacy code used `ActionIdentifyingAttributes`.
*/
case class Action(a: org.openqa.selenium.interactions.Action) {

Expand Down
12 changes: 12 additions & 0 deletions src/main/scala/de/retest/guistatemachine/api/GuiStateMachine.scala
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,16 @@ trait GuiStateMachine {
* @return The current state which the transition of a leads to.
*/
def executeAction(from: State, a: Action, descriptors: Descriptors, neverExploredActions: Set[Action]): State

/**
* Can be used by the GA to generate new test cases.
* @return All actions which have not been explored yet.
*/
def getAllNeverExploredActions: scala.collection.mutable.Set[Action]

/**
* In the legacy code this was only used to show the number of actions which have been explored by Monkey Testing.
* @return All actions which have been explored and therefore have a corresponding transition.
*/
def getAllExploredActions: scala.collection.mutable.Set[Action]
}
22 changes: 21 additions & 1 deletion src/main/scala/de/retest/guistatemachine/api/State.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package de.retest.guistatemachine.api

import scala.util.Random

/**
* A state should be identified by its corresponding [[Descriptors]].
* It consists of actions which have not been explored yet and transitions to states which build up the state machine.
Expand All @@ -23,13 +25,31 @@ trait State {
*/
def getTransitions: Map[Action, Set[State]]

/**
* This was used in the legacy code for Monkey testing.
* @return Returns a random action or an empty value if there are none left.
*/
def getRandomAction(): Option[Action] = {
val r = getAllActions.toVector
if (r.isEmpty) { None } else {
val rnd = new Random()
Some(r(rnd.nextInt(r.size)))
}
}

/**
* Adds a new transition to the state which is only allowed by calling the methods of [[GuiStateMachine]].
* @param a The action which represents the transition's consumed symbol.
* @param to The state which the transition leads t o.
*/
private[api] def addTransition(a: Action, to: State): Unit

/**
* This was named `getRandomActions` in the legacy code but actually returned all actions.
* @return All actions (explored + unexplored).
*/
private def getAllActions(): Set[Action] = getNeverExploredActions ++ getTransitions.keySet

/**
* Overriding this method is required to allow the usage of a set of states.
* Comparing the descriptors should check for the equality of all root elements which compares the identifying attributes and the contained components
Expand All @@ -47,4 +67,4 @@ trait State {
override def hashCode(): Int = this.getDescriptors.hashCode()

override def toString: String = s"descriptors=${getDescriptors},neverExploredActions=${getNeverExploredActions},transitions=${getTransitions}"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,27 @@ package de.retest.guistatemachine.api.impl
import de.retest.guistatemachine.api.{Action, Descriptors, GuiStateMachine, State}

import scala.collection.mutable.HashMap
import scala.collection.mutable.HashSet

class GuiStateMachineImpl extends GuiStateMachine {
val states = new HashMap[Descriptors, State]

/**
* In the legacy code we had `getAllNeverExploredActions` which had to collect them from all states and make sure they were never executed.
* Storing them directly in a set improves efficiency.
*/
val allNeverExploredActions = new HashSet[Action]

/**
* The legacy code stored execution counters for every action.
*/
val allExploredActions = new HashSet[Action]

override def getState(descriptors: Descriptors, neverExploredActions: Set[Action]): State = {
if (states.contains(descriptors)) {
states(descriptors)
} else {
allNeverExploredActions ++= (neverExploredActions -- allExploredActions)
val s = new StateImpl(descriptors, neverExploredActions)
states += (descriptors -> s)
s
Expand All @@ -19,7 +32,13 @@ class GuiStateMachineImpl extends GuiStateMachine {

override def executeAction(from: State, a: Action, descriptors: Descriptors, neverExploredActions: Set[Action]): State = {
val to = getState(descriptors, neverExploredActions)
allExploredActions += a
allNeverExploredActions -= a
from.addTransition(a, to)
to
}

override def getAllNeverExploredActions: scala.collection.mutable.Set[Action] = allNeverExploredActions

override def getAllExploredActions: scala.collection.mutable.Set[Action] = allExploredActions
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,4 @@ class StateImpl(val descriptors: Descriptors, var neverExploredActions: Set[Acti
transitions = transitions + (a -> (transitions(a) + to))
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,4 @@ object WebServer extends App with RestService {
.onComplete(_ => system.terminate()) // and shutdown when done
case None => println("Missing config.")
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,42 +7,59 @@ class GuiStateMachineImplSpec extends AbstractApiSpec {
val sut = new GuiStateMachineImpl
val rootElementA = getRootElement("a")
val rootElementB = getRootElement("b")
val rootElementC = getRootElement("c")
val action0Mock = getAction()
val action1Mock = getAction()

"GuiStateMachine" should {
"get an initial state" in {
val initial = sut.getState(getDescriptors, getNeverExploredActions)
initial.getNeverExploredActions.size shouldEqual 2
initial.getTransitions.size shouldEqual 0
}

"add two transitions to two new states for the same action" in {
"add two transitions to two new states for the same action and one transition to another state for another action" in {
val initialDescriptors = getDescriptors
val initial = sut.getState(getDescriptors, getNeverExploredActions)
sut.getAllExploredActions.size shouldEqual 0
sut.getAllNeverExploredActions.size shouldEqual 2

// execute action0Mock for the first time
val s0Descriptors = Descriptors(Set(rootElementA))
// It should be a new state and not the same again.
initialDescriptors.equals(s0Descriptors) shouldEqual false
val s0 = sut.executeAction(initial, Action(action0Mock), s0Descriptors, getNeverExploredActions)
initial.getNeverExploredActions.size shouldEqual 1
initial.getTransitions.size shouldEqual 1
initial.getTransitions(Action(action0Mock)).size shouldEqual 1
s0.getNeverExploredActions.size shouldEqual 2
s0.getTransitions.size shouldEqual 0
sut.getAllExploredActions.size shouldEqual 1
sut.getAllNeverExploredActions.size shouldEqual 1

// execute action0Mock for the second time
val s1Descriptors = Descriptors(Set(rootElementB))
// It should be a new state and not the same again.
initialDescriptors.equals(s1Descriptors) shouldEqual false
val s1 = sut.executeAction(initial, Action(action0Mock), s1Descriptors, getNeverExploredActions)
initial.getNeverExploredActions.size shouldEqual 1
initial.getTransitions.size shouldEqual 1
initial.getTransitions(Action(action0Mock)).size shouldEqual 2
s1.getNeverExploredActions.size shouldEqual 2
s1.getTransitions.size shouldEqual 0
sut.getAllExploredActions.size shouldEqual 1
sut.getAllNeverExploredActions.size shouldEqual 1

// execute action1Mock for the first time
val s2Descriptors = Descriptors(Set(rootElementC))
val s2 = sut.executeAction(initial, Action(action1Mock), s2Descriptors, getNeverExploredActions)
initial.getNeverExploredActions.size shouldEqual 0
initial.getTransitions.size shouldEqual 2
initial.getTransitions(Action(action1Mock)).size shouldEqual 1
s2.getNeverExploredActions.size shouldEqual 2
s2.getTransitions.size shouldEqual 0
sut.getAllExploredActions.size shouldEqual 2
sut.getAllNeverExploredActions.size shouldEqual 0
}

"store a state for the second access" in {
val initialDescriptors = getDescriptors
val initialFromAccess0 = sut.getState(getDescriptors, getNeverExploredActions)
val initialFromAccess1 = sut.getState(getDescriptors, getNeverExploredActions)
initialFromAccess0 shouldEqual initialFromAccess1
}
}

def getDescriptors: Descriptors = Descriptors(Set(rootElementA, rootElementB))
def getDescriptors: Descriptors = Descriptors(Set(rootElementA, rootElementB, rootElementC))
def getNeverExploredActions: Set[Action] = Set(Action(action0Mock), Action(action1Mock))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,40 @@ package de.retest.guistatemachine.api.impl

import de.retest.guistatemachine.api.{AbstractApiSpec, Action, Descriptors}

import scala.collection.immutable.HashSet

class StateImplSpec extends AbstractApiSpec {

val descriptorsA = Descriptors(HashSet(getRootElement("a")))
val descriptorsB = Descriptors(HashSet(getRootElement("b")))
val descriptorsA = Descriptors(Set(getRootElement("a")))
val descriptorsB = Descriptors(Set(getRootElement("b")))
val action0Mock = getAction()
val action1Mock = getAction()

"StateImpl" should {
"not equal" in {
val s0 = new StateImpl(descriptorsA, HashSet(Action(action0Mock)))
val s1 = new StateImpl(descriptorsB, HashSet(Action(action1Mock)))
val s0 = new StateImpl(descriptorsA, Set(Action(action0Mock)))
val s1 = new StateImpl(descriptorsB, Set(Action(action1Mock)))
s0.equals(s1) shouldEqual false
s0.equals(null) shouldEqual false
}

"equal" in {
val s0 = new StateImpl(descriptorsA, HashSet(Action(action0Mock)))
val s1 = new StateImpl(descriptorsA, HashSet(Action(action1Mock)))
val s0 = new StateImpl(descriptorsA, Set(Action(action0Mock)))
val s1 = new StateImpl(descriptorsA, Set(Action(action1Mock)))
s0.equals(s1) shouldEqual true
}

"be converted into a string" in {
val s0 = new StateImpl(descriptorsA, HashSet(Action(action0Mock)))
val s0 = new StateImpl(descriptorsA, Set(Action(action0Mock)))
s0.toString shouldEqual "descriptors=Descriptors(Set()),neverExploredActions=Set(Selenium Action),transitions=Map()"
}

"have a random action" in {
val s0 = new StateImpl(descriptorsA, Set(Action(action0Mock)))
s0.getRandomAction().isDefined shouldEqual true
}

"have no random action" in {
val s0 = new StateImpl(descriptorsA, Set())
s0.getRandomAction().isEmpty shouldEqual true
}
}
}
}

0 comments on commit 770af5b

Please sign in to comment.