Skip to content

Commit 6fa73a4

Browse files
author
Alexey Shcherbakov
committed
W1 тесты
1 parent 94b1484 commit 6fa73a4

File tree

10 files changed

+337
-3
lines changed

10 files changed

+337
-3
lines changed

build.sbt

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,9 @@ val commonSettings = Seq(
1616
"-language:_",
1717
"-Xexperimental"),
1818

19-
libraryDependencies += "org.scalactic" %% "scalactic" % "3.0.1",
20-
libraryDependencies += "org.scalatest" %% "scalatest" % "3.0.1" % "test",
19+
libraryDependencies += "org.scalactic" %% "scalactic" % "3.0.4",
20+
libraryDependencies += "org.scalamock" %% "scalamock" % "4.1.0" % "test",
21+
libraryDependencies += "org.scalatest" %% "scalatest" % "3.0.4" % "test",
2122
libraryDependencies += "org.scalacheck" %% "scalacheck" % "1.13.4" % "test"
2223
)
2324

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

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

40-
val lecture8 = project.in(file("./lecture8")).settings(commonSettings)
41+
val lecture8 = project.in(file("./lecture8")).settings(commonSettings)
42+
43+
val workshop1 = project.in(file("./workshop1")).settings(commonSettings)
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package w1
2+
3+
import scala.concurrent.{ExecutionContext, Future}
4+
5+
trait Controller[Req, Resp] {
6+
def apply(request: Req)(implicit ec: ExecutionContext): Future[Resp]
7+
}
8+
9+
sealed trait Request
10+
11+
object Request {
12+
13+
case class Add(item: Data) extends Request
14+
15+
case class Get(key: String) extends Request
16+
17+
case object PrintAll extends Request
18+
19+
case object GroupByValue extends Request
20+
21+
}
22+
23+
trait Printer {
24+
def println(v: Any): Unit
25+
}
26+
27+
class ConsolePrinter extends Printer {
28+
override def println(v: Any): Unit = Console.println(v)
29+
}
30+
31+
final class DataController(storage: DataRepository)(implicit printer: Printer) extends Controller[Request, Unit] {
32+
override def apply(request: Request)(implicit ec: ExecutionContext): Future[Unit] = request match {
33+
case Request.PrintAll => storage.all().map(_.foreach(printer.println))
34+
case Request.GroupByValue => storage.all().map(_.groupBy(_.value).foreach(p => printer.println(p._2.toList)))
35+
case Request.Add(item) => storage.put(item)
36+
case Request.Get(key) => storage.get(key).map(printer.println).recover {
37+
case _: NoSuchElementException => printer.println("Not Found")
38+
}
39+
}
40+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package w1
2+
3+
trait Encoder[T] {
4+
def encode(item: T): Array[Byte]
5+
}
6+
7+
trait Decoder[T] {
8+
def decode(raw: Array[Byte]): T
9+
}
10+
11+
trait Converter[T] extends Encoder[T] with Decoder[T]
12+
13+
final class TsvDataConverter extends Converter[Data] {
14+
override def decode(raw: Array[Byte]): Data = {
15+
val parts = new String(raw).split('\t')
16+
Data(parts(0), parts(1))
17+
}
18+
19+
override def encode(item: Data): Array[Byte] = s"${item.key}\t${item.value}".getBytes
20+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package w1
2+
3+
final case class Data(key: String, value: String)
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package w1
2+
3+
import scala.concurrent.Await
4+
import scala.concurrent.duration.Duration
5+
6+
object Main extends App {
7+
8+
import Request._
9+
import scala.concurrent.ExecutionContext.Implicits.global
10+
11+
val repo = new MemoryDataRepository
12+
implicit val printer = new ConsolePrinter
13+
val controller = new DataController(repo)
14+
15+
val f = for {
16+
_ <- controller(Add(Data("a", "b")))
17+
_ <- controller(Add(Data("d", "b")))
18+
_ <- controller(Add(Data("e", "b")))
19+
_ <- controller(Add(Data("b", "c")))
20+
_ <- controller(PrintAll)
21+
_ <- controller(GroupByValue)
22+
_ <- controller(Get("a"))
23+
_ <- controller(Get("c"))
24+
} yield {}
25+
26+
Await.ready(f, Duration.Inf)
27+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package w1
2+
3+
import java.util.concurrent.ConcurrentHashMap
4+
5+
import scala.collection.JavaConverters._
6+
import scala.concurrent.{ExecutionContext, Future}
7+
8+
trait Repository[T, ID] {
9+
def put(item: T): Future[Unit]
10+
11+
def get(id: ID): Future[T]
12+
13+
def all(): Future[Seq[T]]
14+
}
15+
16+
trait DataRepository extends Repository[Data, String]
17+
18+
class MemoryDataRepository(implicit ec: ExecutionContext) extends DataRepository {
19+
private[this] val storage = new ConcurrentHashMap[String, Data]()
20+
21+
override def put(item: Data): Future[Unit] = Future {
22+
storage.put(item.key, item)
23+
}
24+
25+
override def get(key: String): Future[Data] = Future {
26+
Option(storage.get(key)).getOrElse(throw new NoSuchElementException(s"$key not found"))
27+
}
28+
29+
override def all(): Future[Seq[Data]] = Future {
30+
storage.values().asScala.toSeq
31+
}
32+
}
33+
34+
class PersistentDataRepository(storage: Storage[Data])(implicit ec: ExecutionContext) extends DataRepository {
35+
override def put(item: Data): Future[Unit] = storage.write(item)
36+
37+
override def get(key: String): Future[Data] = storage.readAll.map(_.find(_.key == key) match {
38+
case Some(item) => item
39+
case _ => throw new NoSuchElementException(s"$key not found")
40+
})
41+
42+
override def all(): Future[Seq[Data]] = storage.readAll
43+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package w1
2+
3+
import java.io.{FileInputStream, FileOutputStream}
4+
5+
import scala.concurrent.{ExecutionContext, Future}
6+
import scala.io.Source
7+
8+
trait Storage[T] {
9+
def readAll: Future[Seq[T]]
10+
11+
def write(data: T): Future[Unit]
12+
}
13+
14+
final class FileStorage[T](fileName: String)(implicit ec: ExecutionContext, converter: Converter[T]) extends Storage[T] {
15+
override def readAll: Future[Seq[T]] = Future {
16+
Option(new FileInputStream(fileName)) match {
17+
case Some(stream) =>
18+
Source.fromInputStream(stream).getLines().map(line => converter.decode(line.getBytes)).toList
19+
case _ => Seq.empty
20+
}
21+
}
22+
23+
override def write(data: T): Future[Unit] = Future {
24+
val stream = new FileOutputStream(fileName, true)
25+
stream.write(converter.encode(data))
26+
stream.write('\n')
27+
}
28+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package w1
2+
3+
import org.scalamock.scalatest.MockFactory
4+
import org.scalatest.concurrent.ScalaFutures
5+
import org.scalatest.time.{Millis, Seconds, Span}
6+
import org.scalatest.{FlatSpec, Matchers}
7+
8+
import scala.concurrent.Future
9+
10+
class ControllerSpec extends FlatSpec with ScalaFutures with Matchers with MockFactory {
11+
12+
import Request._
13+
14+
import scala.concurrent.ExecutionContext.Implicits.global
15+
16+
implicit val defaultPatience: PatienceConfig = PatienceConfig(timeout = Span(2, Seconds), interval = Span(5, Millis))
17+
18+
trait Context {
19+
val repo: DataRepository = mock[DataRepository]
20+
implicit val printer: Printer = mock[ConsolePrinter]
21+
val controller = new DataController(repo)
22+
}
23+
24+
"DataController" should "add new item" in new Context {
25+
repo.put _ expects Data("a", "b") returning Future.unit
26+
controller(Add(Data("a", "b"))).futureValue
27+
}
28+
29+
"DataController" should "print item by key" in new Context {
30+
repo.get _ expects "a" returning Future.successful(Data("a", "b"))
31+
printer.println _ expects Data("a", "b")
32+
controller(Get("a")).futureValue
33+
}
34+
35+
"DataController" should "print not found for wrong key" in new Context {
36+
repo.get _ expects "a" returning Future.failed(new NoSuchElementException())
37+
printer.println _ expects "Not Found"
38+
controller(Get("a")).futureValue
39+
}
40+
41+
"DataController" should "print all items" in new Context {
42+
repo.all _ expects() returning Future.successful(Seq(Data("a", "b"), Data("b", "a")))
43+
inSequence {
44+
printer.println _ expects Data("a", "b")
45+
printer.println _ expects Data("b", "a")
46+
}
47+
48+
controller(PrintAll).futureValue
49+
}
50+
51+
"DataController" should "print grouped by value" in new Context {
52+
repo.all _ expects() returning Future.successful(Seq(Data("a", "b"), Data("c", "b"), Data("b", "a")))
53+
inSequence {
54+
printer.println _ expects List(Data("a", "b"), Data("c", "b"))
55+
printer.println _ expects List(Data("b", "a"))
56+
}
57+
58+
controller(GroupByValue).futureValue
59+
}
60+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package w1
2+
3+
import org.scalamock.scalatest.MockFactory
4+
import org.scalatest.concurrent.ScalaFutures
5+
import org.scalatest.time.{Millis, Seconds, Span}
6+
import org.scalatest.{FlatSpec, Matchers}
7+
8+
import scala.concurrent.Future
9+
import scala.concurrent.ExecutionContext.Implicits.global
10+
11+
class RepositorySpec extends FlatSpec with ScalaFutures with Matchers with MockFactory {
12+
13+
trait ContextA {
14+
val repo = new MemoryDataRepository()
15+
}
16+
17+
trait ContextB {
18+
val storage = mock[Storage[Data]]
19+
val repo = new PersistentDataRepository(storage)
20+
}
21+
22+
implicit val defaultPatience = PatienceConfig(timeout = Span(2, Seconds), interval = Span(5, Millis))
23+
24+
"MemoryRepository" should "store item" in new ContextA {
25+
repo.put(Data("foo", "bar")).map(_ => succeed)
26+
}
27+
28+
"MemoryRepository" should "return stored item" in new ContextA {
29+
val f = for {
30+
_ <- repo.put(Data("foo", "bar"))
31+
item <- repo.get("foo")
32+
} yield item.key
33+
34+
f.futureValue shouldBe "foo"
35+
}
36+
37+
"MemoryRepository" should "fail for unknown key" in new ContextA {
38+
repo.get("foo").failed.futureValue shouldBe a[NoSuchElementException]
39+
}
40+
41+
"PersistentDataRepository" should "store item via storage" in new ContextB {
42+
storage.write _ expects Data("foo", "bar") returning Future.unit
43+
repo.put(Data("foo", "bar")).map(_ => succeed).futureValue
44+
}
45+
46+
"PersistentDataRepository" should "return item stored item via storage" in new ContextB {
47+
storage.write _ expects Data("foo", "bar") returning Future.unit
48+
storage.readAll _ expects() returning Future.successful(Seq(Data("foo", "bar")))
49+
50+
val f = for {
51+
_ <- repo.put(Data("foo", "bar"))
52+
item <- repo.get("foo")
53+
} yield item.key
54+
55+
f.futureValue shouldBe "foo"
56+
}
57+
58+
"PersistentDataRepository" should "fail for unknown key" in new ContextB {
59+
storage.readAll _ expects() returning Future.successful(Seq.empty)
60+
61+
repo.get("foo").failed.futureValue shouldBe a[NoSuchElementException]
62+
}
63+
64+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package w1
2+
3+
import java.io.File
4+
5+
import org.scalamock.scalatest.AsyncMockFactory
6+
import org.scalatest.concurrent.ScalaFutures
7+
import org.scalatest.{AsyncFlatSpec, BeforeAndAfterEach, Matchers}
8+
9+
import scala.io.Source
10+
11+
class StorageSpec extends AsyncFlatSpec with Matchers with AsyncMockFactory with BeforeAndAfterEach with ScalaFutures {
12+
val fileName = "TempFile.txt"
13+
14+
override def afterEach(): Unit = {
15+
val file = new File(fileName)
16+
file.delete()
17+
}
18+
19+
def withContext[T](f: (Converter[Data], Storage[Data]) => T): T = {
20+
implicit val converter: Converter[Data] = mock[Converter[Data]]
21+
val storage = new FileStorage[Data](fileName)
22+
23+
f(converter, storage)
24+
}
25+
26+
"FileStorage" should "write in file" in withContext { (converter, storage) =>
27+
converter.encode _ expects Data("foo", "bar") returning "data".getBytes
28+
storage.write(Data("foo", "bar")).map({ _ =>
29+
val file = new File(fileName)
30+
Source.fromFile(file, "utf-8").mkString shouldBe "data\n"
31+
})
32+
}
33+
34+
"FileStorage" should "read from file" in withContext { (converter, storage) =>
35+
converter.encode _ expects Data("foo", "bar") returning "data".getBytes
36+
converter.decode _ expects where { (data: Array[Byte]) =>
37+
new String(data) == "data"
38+
} returning Data("bar", "foo")
39+
40+
for {
41+
_ <- storage.write(Data("foo", "bar"))
42+
seq <- storage.readAll
43+
} yield seq shouldBe Seq(Data("bar", "foo"))
44+
}
45+
46+
}

0 commit comments

Comments
 (0)