diff --git a/README.md b/README.md index 3ca0d75..ab8b62a 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/build.sbt b/build.sbt index ed152cb..6117e06 100644 --- a/build.sbt +++ b/build.sbt @@ -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 diff --git a/src/main/scala/de/retest/guistatemachine/api/Action.scala b/src/main/scala/de/retest/guistatemachine/api/Action.scala index aa77304..e3432e4 100644 --- a/src/main/scala/de/retest/guistatemachine/api/Action.scala +++ b/src/main/scala/de/retest/guistatemachine/api/Action.scala @@ -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) { diff --git a/src/main/scala/de/retest/guistatemachine/api/GuiStateMachine.scala b/src/main/scala/de/retest/guistatemachine/api/GuiStateMachine.scala index 7517580..145f8a8 100644 --- a/src/main/scala/de/retest/guistatemachine/api/GuiStateMachine.scala +++ b/src/main/scala/de/retest/guistatemachine/api/GuiStateMachine.scala @@ -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] } \ No newline at end of file diff --git a/src/main/scala/de/retest/guistatemachine/api/State.scala b/src/main/scala/de/retest/guistatemachine/api/State.scala index b82047c..3c379e8 100644 --- a/src/main/scala/de/retest/guistatemachine/api/State.scala +++ b/src/main/scala/de/retest/guistatemachine/api/State.scala @@ -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. @@ -23,6 +25,18 @@ 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. @@ -30,6 +44,12 @@ trait State { */ 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 @@ -47,4 +67,4 @@ trait State { override def hashCode(): Int = this.getDescriptors.hashCode() override def toString: String = s"descriptors=${getDescriptors},neverExploredActions=${getNeverExploredActions},transitions=${getTransitions}" -} +} \ No newline at end of file diff --git a/src/main/scala/de/retest/guistatemachine/api/impl/GuiStateMachineImpl.scala b/src/main/scala/de/retest/guistatemachine/api/impl/GuiStateMachineImpl.scala index 61e7235..f852863 100644 --- a/src/main/scala/de/retest/guistatemachine/api/impl/GuiStateMachineImpl.scala +++ b/src/main/scala/de/retest/guistatemachine/api/impl/GuiStateMachineImpl.scala @@ -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 @@ -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 } \ No newline at end of file diff --git a/src/main/scala/de/retest/guistatemachine/api/impl/StateImpl.scala b/src/main/scala/de/retest/guistatemachine/api/impl/StateImpl.scala index 711c132..aed3817 100644 --- a/src/main/scala/de/retest/guistatemachine/api/impl/StateImpl.scala +++ b/src/main/scala/de/retest/guistatemachine/api/impl/StateImpl.scala @@ -25,4 +25,4 @@ class StateImpl(val descriptors: Descriptors, var neverExploredActions: Set[Acti transitions = transitions + (a -> (transitions(a) + to)) } } -} +} \ No newline at end of file diff --git a/src/main/scala/de/retest/guistatemachine/rest/WebServer.scala b/src/main/scala/de/retest/guistatemachine/rest/WebServer.scala index f53775f..3a261ea 100644 --- a/src/main/scala/de/retest/guistatemachine/rest/WebServer.scala +++ b/src/main/scala/de/retest/guistatemachine/rest/WebServer.scala @@ -50,4 +50,4 @@ object WebServer extends App with RestService { .onComplete(_ => system.terminate()) // and shutdown when done case None => println("Missing config.") } -} +} \ No newline at end of file diff --git a/src/test/scala/de/retest/guistatemachine/api/impl/GuiStateMachineImplSpec.scala b/src/test/scala/de/retest/guistatemachine/api/impl/GuiStateMachineImplSpec.scala index ed4fa45..4ca2835 100644 --- a/src/test/scala/de/retest/guistatemachine/api/impl/GuiStateMachineImplSpec.scala +++ b/src/test/scala/de/retest/guistatemachine/api/impl/GuiStateMachineImplSpec.scala @@ -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)) -} +} \ No newline at end of file diff --git a/src/test/scala/de/retest/guistatemachine/api/impl/StateImplSpec.scala b/src/test/scala/de/retest/guistatemachine/api/impl/StateImplSpec.scala index 4efb196..54a0ae6 100644 --- a/src/test/scala/de/retest/guistatemachine/api/impl/StateImplSpec.scala +++ b/src/test/scala/de/retest/guistatemachine/api/impl/StateImplSpec.scala @@ -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 + } } -} +} \ No newline at end of file