need help in following project where two classes Task and To-Do-List.
I am getting following errors. Trying to resolve but still coming up
Enter an option3
Enter a task:hello
Traceback (most recent call last):
File "/Users/vrishabpatel/Desktop/dominos/pythonExamples/toDoList/menu.py", line 75, in
Menu().run()
File "/Users/vrishabpatel/Desktop/dominos/pythonExamples/toDoList/menu.py", line 43, in run
action()
File "/Users/vrishabpatel/Desktop/dominos/pythonExamples/toDoList/menu.py", line 60, in add_tasks
self.toDoList.new_task(task_name, complete="N")
File "/Users/vrishabpatel/Desktop/dominos/pythonExamples/toDoList/toDoList.py", line 37, in new_task
self.tasks.append(Task(task_name, complete))
AttributeError: 'ToDoList' object has no attribute 'tasks'
from datetime import datetime
"""To Do List Programe"""
"""Represent a Tasks in the To-DoList.
match against a string in searches and store each
tasks"""
last_id = 0
class Task:
def __init__(self, task_name, complete=""):
self.task_name = task_name
self.complete = "N"
self.date_created = datetime.today().strftime('%d-%m-%y')
global last_id
last_id += 1
self.id = last_id
def match_task(self, filter):
"""Determine if this note matches the filter
text. Return True if it matches, False otherwise.
Search is not case sensitive and matches any word in the tasks. """
return filter.lower() in self.task_name.lower()
class ToDoList:
"""Represent a collection of tasks that
can be searched, modified and complete and deleted """
def __int__(self):
self.tasks = []
def new_task(self, task_name, complete):
"""Create new task and add it to the list"""
self.tasks.append(Task(task_name, complete))
def _find_task(self, task_id):
"""locate the task with given id"""
for task_name in self.tasks:
if str(task_name.id) == str(task_name.id):
return task_name
return None
def modify_task(self, task_id, task_name):
task_name = self._find_task(task_id)
if task_name:
task_name.task_name = task_name
return True
return False
def delete_task(self, task_id, complete):
task = self._find_task(task_id)
if task:
task.complete = "Y"
return self.tasks.remove(task_id-1)
return False
def search(self, filter):
"""Find all task that match the given
fliter string """
return [task for task in self.tasks if task.match(filter)]
And Menu class as follows...
"""Main File to Run the programe"""
import sys
from toDoList import ToDoList
class Menu:
"""Display a menu and respond to choices when
run """
def __init__(self):
self.toDoList = ToDoList()
self.choices = {
"1": self.show_tasks,
"2": self.search_tasks,
"3": self.add_tasks,
"4": self.delete_tasks,
"5": self.quit,
}
def display_menu(self):
print(
"""
To Do List menu
===============
1. Show all Tasks
2. Search Tasks
3. Add Tasks
4. Delete Tasks
5. Quit
"""
)
def run(self):
"""Display the menu and repond to the choices"""
while True:
self.display_menu()
choice = input("Enter an option")
action = self.choices.get(choice)
if action:
action()
else:
print("{0} is not a valid choice".format(choice))
def show_tasks(self, tasks=None):
if not tasks:
tasks = self.toDoList.tasks
for task in tasks:
print("{0}: {1}".format(task.id, task))
def search_tasks(self):
filter = input("Search tasks:")
tasks = self.toDoList.search(filter)
self.show_tasks(tasks)
def add_tasks(self):
task_name = input("Enter a task:")
self.toDoList.new_task(task_name, complete="N")
print("Your task has been added:")
def delete_tasks(self):
id = input("Enter a task id:")
task = input("Enter task name:")
if task:
self.toDoList.delete_task(id, task)
def quit(self):
print("Thank you for using To-Do-List today")
sys.exit(0)
if __name__ == "__main__":
Menu().run()
You have a typo in your class definition
class ToDoList:
"""Represent a collection of tasks that
can be searched, modified and complete and deleted """
def __int__(self):
self.tasks = []
should be
class ToDoList:
"""Represent a collection of tasks that
can be searched, modified and complete and deleted """
def __init__(self):
self.tasks = []
The __init__ doesn't get called (because of a typo) and the tasks attribute never gets created.
Related
I have a class 'MyClass' with code as below.
class MyClass:
def __init__(update_type_id='1')
self.update_type_id = update_type_id
self._cursor = <database_connection).cursor()
def update_start_dt(self):
self._update_job_ctrl_start_dt()
def _update_job_ctrl_start_dt(self):
update_sql, v_1 = self._get_update_sql(self._update_type_id)
logger.debug(f'Update sql: {update_sql} and v_1: {v_1}')
def _get_update_sql(self, update_type_id: int) -> Tuple:
sql = f"SELECT start_sql, end_sql FROM <database.table> where update_type_key = {self._update_type_id}"
self._run_sql(sql)
record = self._cursor.fetchone()
if record:
return record
else:
logger.error(f'Record Not Found. Update type key ({update_type_id}) not found in the table in the database')
raise Exception
def _run_sql(self, sql_statement: str):
try:
self._cursor.execute(sql_statement)
except (Exception, Error) as e:
logger.error(f'Error {e} encountered when reading from table')
raise e
I am trying to write a test function using pytest-mock which will test the update_start_dt method. The method internally invokes a series of private methods and I am having difficulty in mocking the code which runs through all the private methods. Can anyone help me to understand in what all ways we can mock?
I tried to refer multiple online websites but couldn't get a complete picture.
class TestMyClass:
def test_update_start_dt(mocker,mock_get_connection,mock_run_sql):
mock_manager = mocker.Mock()
mock_get_update_sql = mock_manager.patch('MyClass._get_update_sql')
mock_get_update_sql.return_value = ('123','234')
myclass = MyClass(update_type_id='1')
myclass.update_start_dt()
I am getting error as below for above test code
update_sql, v_1 = self._get_update_sql(self._update_type_id)
ValueError: not enough values to unpack (expected 2, got 0)
The issue here is that you are patching on a Mock object that you are creating, for the purposes of your test you do not need to explicitly create a Mock object. Shown below is how you would test it instead, where we patch directly on the class.
class MyClass:
def __init__(self, update_type_id='1'):
self._update_type_id = update_type_id
self._cursor = None
def update_start_dt(self):
self._update_job_ctrl_start_dt()
def _update_job_ctrl_start_dt(self):
update_sql, v_1 = self._get_update_sql(self._update_type_id)
logger.debug(f'Update sql: {update_sql} and v_1: {v_1}')
def _get_update_sql(self, update_type_id: int):
sql = f"SELECT start_sql, end_sql FROM <database.table> where update_type_key = {self._update_type_id}"
self._run_sql(sql)
record = self._cursor.fetchone()
if record:
return record
else:
logger.error(f'Record Not Found. Update type key ({update_type_id}) not found in the table in the database')
raise Exception
def _run_sql(self, sql_statement: str):
try:
self._cursor.execute(sql_statement)
except (Exception, Error) as e:
logger.error(f'Error {e} encountered when reading from table')
raise e
def test_update_start_dt(mocker):
mock_get_update_sql = mocker.patch.object(MyClass, "_get_update_sql")
mock_get_update_sql.return_value = ("123", "234")
myclass = MyClass(update_type_id='1')
myclass.update_start_dt()
=========================================== test session starts ============================================
platform darwin -- Python 3.8.9, pytest-7.0.1, pluggy-1.0.0
rootdir: ***
plugins: mock-3.7.0
collected 1 item
test_script.py . [100%]
======================================= 1 passed, 1 warning in 0.01s =======================================
Your code would work if you called the Mock object you created instead of the class. That is shown below.
def test_update_start_dt(mocker):
mock_manager = mocker.Mock()
mock_get_update_sql = mock_manager.patch('MyClass._get_update_sql')
mock_get_update_sql.return_value = ('123','234')
# Notice how we use `mock_manager` instead of MyClass
# tests will now pass
myclass = mock_manager(update_type_id='1')
myclass.update_start_dt()
Hopefully you see what the issue is now.
When doing stateful testing with ScalaCheck the library can shrink the commands needed to find a certain bug. Like in the counter example from the userguide: https://github.com/typelevel/scalacheck/blob/master/doc/UserGuide.md. But what if the commands took arguments and i wanted ScalaCheck to shrink the data inside the commands as well? See the scenario where i am testing the Counter below:
package Counter
case class Counter() {
private var n = 1
def increment(incrementAmount: Int) = {
if (n%100!=0) {
n += incrementAmount
}
}
def get(): Int = n
}
The counter is programmed with a bug. It should not increment with the given amount if n%100 == 0. So if the value of n is x*100, where x is any positive integer, the counter is not incremented. I am testing the counter with the ScalaCheck stateful test below:
import Counter.Counter
import org.scalacheck.commands.Commands
import org.scalacheck.{Gen, Prop}
import scala.util.{Success, Try}
object CounterCommands extends Commands {
type State = Int
type Sut = Counter
def canCreateNewSut(newState: State, initSuts: Traversable[State],
runningSuts: Traversable[Sut]): Boolean = true
def newSut(state: State): Sut = new Counter
def destroySut(sut: Sut): Unit = ()
def initialPreCondition(state: State): Boolean = true
def genInitialState: Gen[State] = Gen.const(1)
def genCommand(state: State): Gen[Command] = Gen.oneOf(Increment(Gen.chooseNum(1, 200000).sample.get), Get)
case class Increment(incrementAmount: Int) extends UnitCommand {
def run(counter: Sut) = counter.increment(incrementAmount)
def nextState(state: State): State = {state+incrementAmount}
def preCondition(state: State): Boolean = true
def postCondition(state: State, success: Boolean) = success
}
case object Get extends Command {
type Result = Int
def run(counter: Sut): Result = counter.get()
def nextState(state: State): State = state
def preCondition(state: State): Boolean = true
def postCondition(state: State, result: Try[Int]): Prop = result == Success(state)
}
}
Everytime the increment command is chosen it is given some arbitrary integer between 1 and 200000 as argument. Running the test gave the following output:
! Falsified after 28 passed tests.
> Labels of failing property:
initialstate = 1
seqcmds = (Increment(1); Increment(109366); Increment(1); Increment(1); Inc
rement(104970); Increment(27214); Increment(197045); Increment(1); Increm
ent(54892); Get => 438600)
> ARG_0: Actions(1,List(Increment(1), Increment(109366), Increment(1), Incr
ement(1), Increment(104970), Increment(27214), Increment(197045), Increme
nt(1), Increment(54892), Get),List())
> ARG_0_ORIGINAL: Actions(1,List(Get, Get, Increment(1), Increment(109366),
Get, Get, Get, Get, Increment(1), Get, Increment(1), Increment(104970),
Increment(27214), Get, Increment(197045), Increment(1), Increment(54892),
Get, Get, Get, Get, Get, Increment(172491), Get, Increment(6513), Get, I
ncrement(57501), Increment(200000)),List())
ScalaCheck did indeed shrink the commands needed to find the bug (as can be seen in ARG_0) but it did not shrink the data inside the commands. It ended up with a much larger counter value (438600) than what is actually needed to find the bug. If the first increment command was given 99 as argument the bug would be found.
Is there any way in ScalaCheck to shrink the data inside the commands when running stateful tests? The ScalaCheck version used is 1.14.1.
EDIT:
I tried simplifying the bug (and only incrementing if n!=10) and added the shrinker that was suggested by Levi and could still not get it to work. The whole runnable code can be seen below:
package LocalCounter
import org.scalacheck.commands.Commands
import org.scalacheck.{Gen, Prop, Properties, Shrink}
import scala.util.{Success, Try}
case class Counter() {
private var n = 1
def increment(incrementAmount: Int) = {
if (n!=10) {
n += incrementAmount
}
}
def get(): Int = n
}
object CounterCommands extends Commands {
type State = Int
type Sut = Counter
def canCreateNewSut(newState: State, initSuts: Traversable[State],
runningSuts: Traversable[Sut]): Boolean = true
def newSut(state: State): Sut = new Counter
def destroySut(sut: Sut): Unit = ()
def initialPreCondition(state: State): Boolean = true
def genInitialState: Gen[State] = Gen.const(1)
def genCommand(state: State): Gen[Command] = Gen.oneOf(Increment(Gen.chooseNum(1, 40).sample.get), Get)
case class Increment(incrementAmount: Int) extends UnitCommand {
def run(counter: Sut) = counter.increment(incrementAmount)
def nextState(state: State): State = {state+incrementAmount}
def preCondition(state: State): Boolean = true
def postCondition(state: State, success: Boolean) = success
}
case object Get extends Command {
type Result = Int
def run(counter: Sut): Result = counter.get()
def nextState(state: State): State = state
def preCondition(state: State): Boolean = true
def postCondition(state: State, result: Try[Int]): Prop = result == Success(state)
}
implicit val shrinkCommand: Shrink[Command] = Shrink({
case Increment(amt) => Shrink.shrink(amt).map(Increment(_))
case Get => Stream.empty
})
}
object CounterCommandsTest extends Properties("CounterCommands") {
CounterCommands.property().check()
}
Running the code gave the following output:
! Falsified after 4 passed tests.
> Labels of failing property:
initialstate = 1
seqcmds = (Increment(9); Increment(40); Get => 10)
> ARG_0: Actions(1,List(Increment(9), Increment(40), Get),List())
> ARG_0_ORIGINAL: Actions(1,List(Increment(9), Increment(34), Increment(40)
, Get),List())
Which is not the minimal example.
You should be able to define a custom Shrink for Command along these lines:
implicit val shrinkCommand: Shrink[Command] = Shrink({
case Increment(amt) => shrink(amt).map(Increment(_))
case Get => Stream.empty
}
Note that, because Stream is deprecated in Scala 2.13, you may need to disable warnings in Scala 2.13 (Scalacheck 1.15 will allow LazyList to be used to define shrinks).
I have created a console-interface application as todo-list in Scala. My data access layer works with Slick 3 and my interface works using simple StdIn methods. But I have some troubles with reading lines. My main menu works fine while inner menu acts weirdly sometimes. In particular, when I enter a command for the first time I do not get any result just the same menu is displayed again. Then I enter any command and I get the result. And if I try to enter some command for the 3d time my program just stops with System.exit.
Here is the code for my interface:
object UserInterface {
def displayMainMenu(): Unit ={
println("Main menu:" + " \n1 - Login" + "\n2 - Exit")
println("\nChoose the operation you want to perform:")
val inputMainMenu = readInt()
buildMainMenu(inputMainMenu)
}
def buildMainMenu(inputNumber: Int) = inputNumber match {
case 1 => enterSystem()
case 2 => System.exit(0)
case _ => println("Your input was wrong. Try again"); displayMainMenu()
}
def enterSystem(): Unit ={
println("Input you login, please:")
val inputLogin = readLine()
println("Input you password, please:")
val inputPassword = readLine()
val checkLogin = Await.result(DAO.checkUserLogin(inputLogin, inputPassword), Duration.Inf).toString
val userId = DAO.selectUserId(inputLogin)
def changeOutputs(checkLogin: String):Unit = checkLogin match {
case "true" => println("You have successfully entered"); displayInnerMenu(); buildMenu(userId)
case "false" => println("Your input for login or password is wrong. Please, try again"); displayMainMenu()
case _ => println("Your input is wrong"); displayMainMenu()
}
changeOutputs(checkLogin)
}
def buildMenu(userId: Long): Unit ={
def chooseOption(number: Int):Unit = number match {
case 1 => displayFinishedTasks(userId)
case 2 => displayUnfinishedTasks(userId)
case 3 => addTask(userId)
case 4 => deleteTask()
case 5 => markTaskAsFinished(userId)
case 6 => displayMainMenu()
case _ => println("Your input is wrong"); displayMainMenu()
}
val inputNum = displayInnerMenu()
chooseOption(inputNum)
}
def displayInnerMenu():Int ={
println("TODO List:" + "\n1 - Display finished tasks" + "\n2 - Display unfinished tasks"
+ "\n3 - Add task" + "\n4 - Delete task" + "\n5 - Mark task as finished" + "\n6 - Get back to the main menu")
println("\nChoose the operation you want to perform:")
val inputNum = readInt()
inputNum
}
def displayAllTasks(id: Long) = {
println()
println("User's tasks:\n" + Await.result(DAO.selectTasksByUser(id), Duration.Inf).toList.toString)
displayInnerMenu()
}
def displayFinishedTasks(id: Long) = {
println()
println("User's finished tasks:\n" + Await.result(DAO.selectFinishedTasks(id), Duration.Inf).toList.toString)
displayInnerMenu()
}
def displayUnfinishedTasks(id: Long) = {
println()
println("User's unfinished tasks:\n" + Await.result(DAO.selectUnfinishedTasks(id), Duration.Inf).toList.toString)
displayInnerMenu()
}
def addTask(id: Long) = {
println()
println("Input the task name you want to create, please:")
val taskName = readLine()
Await.result(DAO.addTask(taskName, id), Duration.Inf)
displayInnerMenu()
}
def deleteTask() = {
println()
println("Choose the task you want to delete, please:")
val taskId = readLong()
Await.result(DAO.deleteTask(Some(taskId)), Duration.Inf)
displayInnerMenu()
}
def markTaskAsFinished(id: Long) = {
println()
println("Choose the task you want to mark as finished, please:")
val taskId = readLong()
Await.result(DAO.finishTask(Some(taskId), id), Duration.Inf)
displayInnerMenu()
}
}
What I want is some kind of infinite cycle so I could perform my commands as many times as I need or set the limit. So what changes I can introduce in this code? I would be very grateful for some help!
Your particular troubles seem to come from the fact that changeOutputs in
enterSystem calls displayInnerMenu which reads an Int from input but does nothing useful with it. Probably you should have called buildMenu in most of the places where displayInnerMenu is called.
Also it seems that you should improve your debugging skills. This is a crucial skill and this code is not that hard to debug.
Taken more broadly this is a complicated topic with no simple best answer. But there are certainly bad ones and unfortunately yours is one of those. The thing I don't like most in your code is big separation in the code between the menu item title and menu item action. (Just imagine what it takes to add new menu item in the middle. Or what would it take to create a deeper menu with some items shared between levels.) So I would re-write most of the code. Being more of an OOP-guy than a FP-guy, I would do something like this:
object UserInterface {
// should be non-generic for simplicity of the rest of the code
trait MenuAndStateNG {
def runMenu(): MenuAndStateNG
}
trait MenuItem[S] {
val name: String
def doAction(state: S, curMenu: MenuAndStateNG): MenuAndStateNG
}
case class Menu[S](header: String, items: Seq[MenuItem[S]]) {}
case class MenuAndState[S](menu: Menu[S], state: S) extends MenuAndStateNG {
def runMenu(): MenuAndStateNG = {
var inputNum: Int = -1
var isFirstRun = true
// we use 1-based indices in the menu
while (inputNum <= 0 || inputNum > menu.items.length) {
if (!isFirstRun) {
println("Your input was wrong. Try again")
}
isFirstRun = false
println(menu.header + ":")
println(menu.items.zipWithIndex.map({ case (item, index) => s"${index + 1} - ${item.name}" }).mkString("\n"))
println("Choose the operation you want to perform:")
inputNum = StdIn.readInt()
}
println()
val nextMenu = menu.items(inputNum - 1).doAction(state, this)
nextMenu
}
}
// most of menu items doesn't change current menu
// let's make it easier to implement
trait SimpleMenuItem[S] extends MenuItem[S] {
override def doAction(state: S, curMenu: MenuAndStateNG): MenuAndStateNG = {
doSimpleAction(state)
curMenu
}
def doSimpleAction(state: S): Unit
}
def start(): Unit = {
var curMenu: MenuAndStateNG = MenuAndState(mainMenu, ())
var isFirstRun = true
while (true) {
if (!isFirstRun) {
println
}
isFirstRun = false
curMenu = curMenu.runMenu()
}
}
private val loginItem = new MenuItem[Unit] {
override val name = "Login"
override def doAction(state: Unit, curMenu: MenuAndStateNG): MenuAndStateNG = {
println("Input you login, please:")
val inputLogin = StdIn.readLine()
println("Input you password, please:")
val inputPassword = StdIn.readLine()
val checkLogin = Await.result(DAO.checkUserLogin(inputLogin, inputPassword), Duration.Inf).toString
val userId = DAO.selectUserId(inputLogin)
checkLogin match {
case "true" =>
println("You have successfully entered")
MenuAndState(userMenu, userId)
case "false" =>
println("Your input for login or password is wrong. Please, try again")
curMenu
case _ =>
println("Your input is wrong")
curMenu
}
}
}
private val exitItem = new MenuItem[Unit] {
override val name = "Exit"
override def doAction(state: Unit, curMenu: MenuAndStateNG): MenuAndStateNG = {
System.exit(0)
null // null is bad but it doesn't matter by now
}
}
private val displayFinishedTasks = new SimpleMenuItem[Int] {
override val name: String = "Display finished tasks"
override def doSimpleAction(state: Int): Unit = {
println("User's finished tasks:\n" + Await.result(DAO.selectFinishedTasks(id), Duration.Inf).toList.toString)
}
}
private val displayUnfinishedTasks = new SimpleMenuItem[Int] {
override val name: String = "Display unfinished tasks"
override def doSimpleAction(state: Int): Unit = {
println("User's unfinished tasks:\n" + Await.result(DAO.selectUnfinishedTasks(id), Duration.Inf).toList.toString)
}
}
private val displayAllTasks = new SimpleMenuItem[Int] {
override val name: String = "Display all tasks"
override def doSimpleAction(state: Int): Unit = {
println("User's tasks:\n" + Await.result(DAO.selectTasksByUser(id), Duration.Inf).toList.toString)
}
}
private val addTask = new SimpleMenuItem[Int] {
override val name: String = "Add task"
override def doSimpleAction(state: Int): Unit = {
println("Input the task name you want to create, please:")
val taskName = readLine()
Await.result(DAO.addTask(taskName, id), Duration.Inf)
}
}
private val deleteTask = new SimpleMenuItem[Int] {
override val name: String = "Delete task"
override def doSimpleAction(state: Int): Unit = {
println("Choose the task you want to delete, please:")
val taskId = readLong()
Await.result(DAO.deleteTask(Some(taskId)), Duration.Inf)
}
}
private val markTaskFinished = new SimpleMenuItem[Int] {
override val name: String = "Mark task as finished"
override def doSimpleAction(state: Int): Unit = {
println("Choose the task you want to mark as finished, please:")
val taskId = readLong()
Await.result(DAO.finishTask(Some(taskId), id), Duration.Inf)
}
}
private val logoutTask = new MenuItem[Int] {
override val name = "Get back to the main menu"
override def doAction(state: Int, curMenu: MenuAndStateNG): MenuAndState[Unit] = {
MenuAndState(mainMenu, ())
}
}
val mainMenu: Menu[Unit] = Menu("Main menu", List(loginItem, exitItem))
val userMenu: Menu[Int] = Menu("User menu", List(
displayAllTasks,
displayFinishedTasks,
displayUnfinishedTasks,
addTask,
deleteTask,
markTaskFinished,
logoutTask))
}
The main ideas are following:
Join menu action and title into a single MenuItem
Let the MenuItem select next "menu state" (MenuAndState)
MenuAndState from the outside looks like MenuAndStateNG - i.e. something that just can be run to get next MenuAndStateNG. From the inside it is split into a "fixed part" (Menu) = header + list of items and "variable part" = state. By introducing this separation I was able to make userMenu actually a constant rather than def
Most of the menu items doesn't change menu and just return to their parent. To simplify code for this scenario curMenu is passed as an argument to the doAction and there is a SimpleMenuItem that just always returns it\
Given such design all you need is:
create a val for each menu item
create mainMenu and userMenu vals effectively as lists of those menu items
run an infinite loop starting from the mainMenu (done in start)
Note that because MenuAndStateNG returns the next MenuAndStateNG from its runMenu I can use an infinite loop instead of deepening the stack on each menu iteration (which is generally a bad idea).
I have the following sample code :
package models
import java.util.concurrent.atomic.AtomicInteger
import scala.collection.mutable.ArrayBuffer
case class Task(id: Int, label: String)
object Task {
private val buffer = new ArrayBuffer[Task]
private val incrementer = new AtomicInteger()
def all(): List[Task] = buffer.toList
def create(label: String): Int = {
val newId = incrementer.incrementAndGet()
buffer += new Task(newId, label)
newId
}
def delete(id: Int): Boolean = {
// TODO : add code
}
}
In method delete I need to find a Task that has id equal to the parameter id and if one is found I need to remove it from the collection and return true from the method. Otherwise (if none is found) I should just return false.
I know how to do this in an imperative language such as C# or Java but Scala stumps me..
PS : The code is strictly used to understand the language and the platform, it sucks too much to be pushed in production. Don't worry.
This is one possible solution, however in this case I think it's also possible to switch to var + immutable ArrayBuffer and use filter. Also note that this code is not thread safe
import java.util.concurrent.atomic.AtomicInteger
import scala.collection.mutable.ArrayBuffer
case class Task(id: Int, label: String)
object Task {
private val buffer = new ArrayBuffer[Task]
private val incrementer = new AtomicInteger()
def all(): List[Task] = buffer.toList
def create(label: String): Int = {
val newId = incrementer.incrementAndGet()
buffer.append(Task(newId, label))
newId
}
def delete(id: Int): Boolean = {
buffer.
find(_.id == id). // find task by id
map(buffer -= _). // remove it from buffer
exists(_ => true) // the same as: map(_ => true).getOrElse(false)
}
}
val id1 = Task.create("aaa")
val id2 = Task.create("bbb")
println(s"Id1 = $id1 Id2 = $id2")
println(s"All = ${Task.all()}")
val deleted = Task.delete(id1)
println(s"Deleted = $deleted")
println(s"All = ${Task.all()}")
println(s"Not Deleted = ${Task.delete(123)}")
Developing functional tests i need to simulate a workflow with result of one step being used as an input for the following one(s). Example is like this:
search for a hotel\room with given criteria
check that request succeeded
check that there are at least some results
pick random room from step 1.
book the room from step 2.
check that request succeeded
cancel the booking from step 3.
check that request succeeded
Key points here are:
we cannot perform 3. without doing 1.
we cannot perform 4. without doing 3.
if a step fails, we should abort the feature
What is the approach to developing a specification for such case?
The easiest thing to do is to have a mutable object representing the process and a sequential Specification:
class HotelSpec extends mutable.Specification { sequential
val hotel = new HotelProcess
"get a room available on Monday" >> ifHotelOk {
val rooms = request(MONDAY)
hotel.selectedRooms = rooms
rooms must not beEmpty
}
"book the room" >> ifHotelOk {
val booking = bookRoom(hotel.selectedRooms.head)
hotel.currentBooking = booking
booking must beOk
}
def ifHotelOk(r: =>Any) = if (hotel.canContinueProcess) {
try { r; hotel.continueProcess }
catch { case t: Throwable => hotel.stopProcess; throw t }
} else skipped("hotel process error in previous steps")
}
[UPDATE]
Here is another way to do it where the var is better encapsulated:
import org.specs2._
import org.specs2.execute._
import org.specs2.specification.FixtureExample
class HotelSpec extends HotelProcessSpec {
"get a room available on Monday" >> { hotel: HP =>
val rooms = request(MONDAY)
rooms must be empty
// update the state of the process at the end of the example
hotel.selectedRoomsAre(rooms)
}
// this example will only execute if the previous step was ok
"book the room" >> { hotel: HP =>
val booking = bookRoom(hotel.selectedRooms.head)
booking.booked must beTrue
}
val MONDAY = "monday"
def request(day: String): Seq[Room] = Seq(Room())
def bookRoom(room: Room) = Booking()
}
/**
* A specification trait encapsulating the process of booking hotel rooms
*/
trait HotelProcessSpec extends mutable.Specification with FixtureExample[HotelProcess] {
sequential
type HP = HotelProcess
private var hotelProcess = HotelProcess()
// if the hotelProcess is returned as the last statement of an Example
// set the new value of the hotelProcess and return Success
implicit def hotelProcessAsResult: AsResult[HotelProcess] = new AsResult[HotelProcess] {
def asResult(hp: =>HotelProcess) =
try { hotelProcess = hp; Success() }
catch { case t: Throwable => hotelProcess = hotelProcess.stop; throw t }
}
/**
* stop executing examples if one previous step failed
*/
protected def fixture[R : AsResult](f: HotelProcess => R): Result = {
if (hotelProcess.continue) {
val result = AsResult(f(hotelProcess))
if (!result.isSuccess) hotelProcess = hotelProcess.stop
result
}
else skipped(" - SKIPPED: can't execute this step")
}
}
case class HotelProcess(selectedRooms: Seq[Room] = Seq(), continue: Boolean = true) {
def stop = copy(continue = false)
def selectedRoomsAre(rooms: Seq[Room]) = copy(selectedRooms = rooms)
}
case class Room(number: Int = 0)
case class Booking(booked: Boolean = true)