Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

W1 тесты #8

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ val commonSettings = Seq(
"-language:_",
"-Xexperimental"),

libraryDependencies += "org.scalactic" %% "scalactic" % "3.0.1",
libraryDependencies += "org.scalatest" %% "scalatest" % "3.0.1" % "test",
libraryDependencies += "org.scalactic" %% "scalactic" % "3.0.4",
libraryDependencies += "org.scalamock" %% "scalamock" % "4.1.0" % "test",
libraryDependencies += "org.scalatest" %% "scalatest" % "3.0.4" % "test",
libraryDependencies += "org.scalacheck" %% "scalacheck" % "1.13.4" % "test"
)

Expand All @@ -37,4 +38,6 @@ val lecture6 = project.in(file("./lecture6")).settings(commonSettings)

val lecture7 = project.in(file("./lecture7")).settings(commonSettings)

val lecture8 = project.in(file("./lecture8")).settings(commonSettings)
val lecture8 = project.in(file("./lecture8")).settings(commonSettings)

val workshop1 = project.in(file("./workshop1")).settings(commonSettings)
40 changes: 40 additions & 0 deletions workshop1/src/main/scala/w1/Controller.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package w1

import scala.concurrent.{ExecutionContext, Future}

trait Controller[Req, Resp] {
def apply(request: Req)(implicit ec: ExecutionContext): Future[Resp]
}

sealed trait Request

object Request {

case class Add(item: Data) extends Request

case class Get(key: String) extends Request

case object PrintAll extends Request

case object GroupByValue extends Request

}

trait Printer {
def println(v: Any): Unit
}

class ConsolePrinter extends Printer {
override def println(v: Any): Unit = Console.println(v)
}

final class DataController(storage: DataRepository)(implicit printer: Printer) extends Controller[Request, Unit] {
override def apply(request: Request)(implicit ec: ExecutionContext): Future[Unit] = request match {
case Request.PrintAll => storage.all().map(_.foreach(printer.println))
case Request.GroupByValue => storage.all().map(_.groupBy(_.value).foreach(p => printer.println(p._2.toList)))
case Request.Add(item) => storage.put(item)
case Request.Get(key) => storage.get(key).map(printer.println).recover {
case _: NoSuchElementException => printer.println("Not Found")
}
}
}
20 changes: 20 additions & 0 deletions workshop1/src/main/scala/w1/Converter.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package w1

trait Encoder[T] {
def encode(item: T): Array[Byte]
}

trait Decoder[T] {
def decode(raw: Array[Byte]): T
}

trait Converter[T] extends Encoder[T] with Decoder[T]

final class TsvDataConverter extends Converter[Data] {
override def decode(raw: Array[Byte]): Data = {
val parts = new String(raw).split('\t')
Data(parts(0), parts(1))
}

override def encode(item: Data): Array[Byte] = s"${item.key}\t${item.value}".getBytes
}
3 changes: 3 additions & 0 deletions workshop1/src/main/scala/w1/Data.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package w1

final case class Data(key: String, value: String)
27 changes: 27 additions & 0 deletions workshop1/src/main/scala/w1/Main.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package w1

import scala.concurrent.Await
import scala.concurrent.duration.Duration

object Main extends App {

import Request._
import scala.concurrent.ExecutionContext.Implicits.global

val repo = new MemoryDataRepository
implicit val printer = new ConsolePrinter
val controller = new DataController(repo)

val f = for {
_ <- controller(Add(Data("a", "b")))
_ <- controller(Add(Data("d", "b")))
_ <- controller(Add(Data("e", "b")))
_ <- controller(Add(Data("b", "c")))
_ <- controller(PrintAll)
_ <- controller(GroupByValue)
_ <- controller(Get("a"))
_ <- controller(Get("c"))
} yield {}

Await.ready(f, Duration.Inf)
}
43 changes: 43 additions & 0 deletions workshop1/src/main/scala/w1/Repository.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package w1

import java.util.concurrent.ConcurrentHashMap

import scala.collection.JavaConverters._
import scala.concurrent.{ExecutionContext, Future}

trait Repository[T, ID] {
def put(item: T): Future[Unit]

def get(id: ID): Future[T]

def all(): Future[Seq[T]]
}

trait DataRepository extends Repository[Data, String]

class MemoryDataRepository(implicit ec: ExecutionContext) extends DataRepository {
private[this] val storage = new ConcurrentHashMap[String, Data]()

override def put(item: Data): Future[Unit] = Future {
storage.put(item.key, item)
}

override def get(key: String): Future[Data] = Future {
Option(storage.get(key)).getOrElse(throw new NoSuchElementException(s"$key not found"))
}

override def all(): Future[Seq[Data]] = Future {
storage.values().asScala.toSeq
}
}

class PersistentDataRepository(storage: Storage[Data])(implicit ec: ExecutionContext) extends DataRepository {
override def put(item: Data): Future[Unit] = storage.write(item)

override def get(key: String): Future[Data] = storage.readAll.map(_.find(_.key == key) match {
case Some(item) => item
case _ => throw new NoSuchElementException(s"$key not found")
})

override def all(): Future[Seq[Data]] = storage.readAll
}
28 changes: 28 additions & 0 deletions workshop1/src/main/scala/w1/Storage.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package w1

import java.io.{FileInputStream, FileOutputStream}

import scala.concurrent.{ExecutionContext, Future}
import scala.io.Source

trait Storage[T] {
def readAll: Future[Seq[T]]

def write(data: T): Future[Unit]
}

final class FileStorage[T](fileName: String)(implicit ec: ExecutionContext, converter: Converter[T]) extends Storage[T] {
override def readAll: Future[Seq[T]] = Future {
Option(new FileInputStream(fileName)) match {
case Some(stream) =>
Source.fromInputStream(stream).getLines().map(line => converter.decode(line.getBytes)).toList
case _ => Seq.empty
}
}

override def write(data: T): Future[Unit] = Future {
val stream = new FileOutputStream(fileName, true)
stream.write(converter.encode(data))
stream.write('\n')
}
}
60 changes: 60 additions & 0 deletions workshop1/src/test/scala/w1/ControllerSpec.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package w1

import org.scalamock.scalatest.MockFactory
import org.scalatest.concurrent.ScalaFutures
import org.scalatest.time.{Millis, Seconds, Span}
import org.scalatest.{FlatSpec, Matchers}

import scala.concurrent.Future

class ControllerSpec extends FlatSpec with ScalaFutures with Matchers with MockFactory {

import Request._

import scala.concurrent.ExecutionContext.Implicits.global

implicit val defaultPatience: PatienceConfig = PatienceConfig(timeout = Span(2, Seconds), interval = Span(5, Millis))

trait Context {
val repo: DataRepository = mock[DataRepository]
implicit val printer: Printer = mock[ConsolePrinter]
val controller = new DataController(repo)
}

"DataController" should "add new item" in new Context {
repo.put _ expects Data("a", "b") returning Future.unit
controller(Add(Data("a", "b"))).futureValue
}

"DataController" should "print item by key" in new Context {
repo.get _ expects "a" returning Future.successful(Data("a", "b"))
printer.println _ expects Data("a", "b")
controller(Get("a")).futureValue
}

"DataController" should "print not found for wrong key" in new Context {
repo.get _ expects "a" returning Future.failed(new NoSuchElementException())
printer.println _ expects "Not Found"
controller(Get("a")).futureValue
}

"DataController" should "print all items" in new Context {
repo.all _ expects() returning Future.successful(Seq(Data("a", "b"), Data("b", "a")))
inSequence {
printer.println _ expects Data("a", "b")
printer.println _ expects Data("b", "a")
}

controller(PrintAll).futureValue
}

"DataController" should "print grouped by value" in new Context {
repo.all _ expects() returning Future.successful(Seq(Data("a", "b"), Data("c", "b"), Data("b", "a")))
inSequence {
printer.println _ expects List(Data("a", "b"), Data("c", "b"))
printer.println _ expects List(Data("b", "a"))
}

controller(GroupByValue).futureValue
}
}
64 changes: 64 additions & 0 deletions workshop1/src/test/scala/w1/RepositorySpec.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package w1

import org.scalamock.scalatest.MockFactory
import org.scalatest.concurrent.ScalaFutures
import org.scalatest.time.{Millis, Seconds, Span}
import org.scalatest.{FlatSpec, Matchers}

import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global

class RepositorySpec extends FlatSpec with ScalaFutures with Matchers with MockFactory {

trait ContextA {
val repo = new MemoryDataRepository()
}

trait ContextB {
val storage = mock[Storage[Data]]
val repo = new PersistentDataRepository(storage)
}

implicit val defaultPatience = PatienceConfig(timeout = Span(2, Seconds), interval = Span(5, Millis))

"MemoryRepository" should "store item" in new ContextA {
repo.put(Data("foo", "bar")).map(_ => succeed)
}

"MemoryRepository" should "return stored item" in new ContextA {
val f = for {
_ <- repo.put(Data("foo", "bar"))
item <- repo.get("foo")
} yield item.key

f.futureValue shouldBe "foo"
}

"MemoryRepository" should "fail for unknown key" in new ContextA {
repo.get("foo").failed.futureValue shouldBe a[NoSuchElementException]
}

"PersistentDataRepository" should "store item via storage" in new ContextB {
storage.write _ expects Data("foo", "bar") returning Future.unit
repo.put(Data("foo", "bar")).map(_ => succeed).futureValue
}

"PersistentDataRepository" should "return item stored item via storage" in new ContextB {
storage.write _ expects Data("foo", "bar") returning Future.unit
storage.readAll _ expects() returning Future.successful(Seq(Data("foo", "bar")))

val f = for {
_ <- repo.put(Data("foo", "bar"))
item <- repo.get("foo")
} yield item.key

f.futureValue shouldBe "foo"
}

"PersistentDataRepository" should "fail for unknown key" in new ContextB {
storage.readAll _ expects() returning Future.successful(Seq.empty)

repo.get("foo").failed.futureValue shouldBe a[NoSuchElementException]
}

}
46 changes: 46 additions & 0 deletions workshop1/src/test/scala/w1/StorageSpec.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package w1

import java.io.File

import org.scalamock.scalatest.AsyncMockFactory
import org.scalatest.concurrent.ScalaFutures
import org.scalatest.{AsyncFlatSpec, BeforeAndAfterEach, Matchers}

import scala.io.Source

class StorageSpec extends AsyncFlatSpec with Matchers with AsyncMockFactory with BeforeAndAfterEach with ScalaFutures {
val fileName = "TempFile.txt"

override def afterEach(): Unit = {
val file = new File(fileName)
file.delete()
}

def withContext[T](f: (Converter[Data], Storage[Data]) => T): T = {
implicit val converter: Converter[Data] = mock[Converter[Data]]
val storage = new FileStorage[Data](fileName)

f(converter, storage)
}

"FileStorage" should "write in file" in withContext { (converter, storage) =>
converter.encode _ expects Data("foo", "bar") returning "data".getBytes
storage.write(Data("foo", "bar")).map({ _ =>
val file = new File(fileName)
Source.fromFile(file, "utf-8").mkString shouldBe "data\n"
})
}

"FileStorage" should "read from file" in withContext { (converter, storage) =>
converter.encode _ expects Data("foo", "bar") returning "data".getBytes
converter.decode _ expects where { (data: Array[Byte]) =>
new String(data) == "data"
} returning Data("bar", "foo")

for {
_ <- storage.write(Data("foo", "bar"))
seq <- storage.readAll
} yield seq shouldBe Seq(Data("bar", "foo"))
}

}