[Scala, Программирование] Применение ZIO ZLayer (перевод)
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
В июле OTUS запускает новый курс «Scala-разработчик», в связи с чем мы подготовили для вас перевод полезного материала.
Новая функция ZLayer в ZIO 1.0.0-RC18+ является значительным улучшением старого паттерна модулей, что делает добавление новых сервисов намного быстрее и легче. Однако при использовании на практике я обнаружил, что может потребоваться какое-то время, чтобы освоить эту идиому.
Ниже приведен аннотированный пример финальной версии моего тестового кода, в котором я рассматриваю ряд вариантов использования. Большое спасибо Адаму Фрейзеру за помощь в оптимизации и облагораживании моей работы. Сервисы преднамеренно упрощены, так что, надеюсь, они будут достаточно понятны для быстрого чтения.
Я предполагаю, что у вас есть базовое понимание ZIO тестов, а также вы ознакомились с основной информацией касательно модулей.
Весь код запускается в zio тестах и представляет собой один файл.
Вот его верхушка:
import zio._
import zio.test._
import zio.random.Random
import Assertion._
object LayerTests extends DefaultRunnableSpec {
type Names = Has[Names.Service]
type Teams = Has[Teams.Service]
type History = Has[History.Service]
val firstNames = Vector( "Ed", "Jane", "Joe", "Linda", "Sue", "Tim", "Tom")
Names
Итак, мы добрались до нашего первого сервиса — Names (Имена)
type Names = Has[Names.Service]
object Names {
trait Service {
def randomName: UIO[String]
}
case class NamesImpl(random: Random.Service) extends Names.Service {
println(s"created namesImpl")
def randomName =
random.nextInt(firstNames.size).map(firstNames(_))
}
val live: ZLayer[Random, Nothing, Names] =
ZLayer.fromService(NamesImpl)
}
package object names {
def randomName = ZIO.accessM[Names](_.get.randomName)
}
Тут все в рамках типичного модульного паттерна.
- Объявите Names как псевдоним типа для Has
- В объекте, определите Service как трейт
- Создайте реализацию (конечно, вы можете создать несколько),
- Создайте ZLayer внутри объекта для данной реализации. Конвенция ZIO имеет тенденцию вызывать их в реальном времени.
- Добавляется объект пакета, который обеспечивает удобное для доступа сокращение.
В live используется ZLayer.fromService, который определяется как:
def fromService[A: Tagged, B: Tagged](f: A => B): ZLayer[Has[A], Nothing, Has[B]
Игнорируя Tagged (это необходимо для работы всего Has/Layers), вы можете видеть, что здесь используется функция f: A => B — которая в данном случае является просто конструктором кейс класса для NamesImpl.
Как вы можете видеть, для работы Names требуется Random из среды zio.
Вот тест:
def namesTest = testM("names test") {
for {
name <- names.randomName
} yield {
assert(firstNames.contains(name))(equalTo(true))
}
}
Он использует ZIO.accessM для извлечения Names из среды. _.get извлекает сервис.
Мы предоставляем Names для теста следующим образом:
suite("needs Names")(
namesTest
).provideCustomLayer(Names.live),
provideCustomLayer добавляет слой Names в существующую среду.
Teams
Суть Teams (Команды) заключается в тестировании зависимостей между модулями, которые мы создали.
object Teams {
trait Service {
def pickTeam(size: Int): UIO[Set[String]]
}
case class TeamsImpl(names: Names.Service) extends Service {
def pickTeam(size: Int) =
ZIO.collectAll(0.until(size).map { _ => names.randomName}).map(_.toSet ) // да, я знаю, что команда может иметь < размер!
}
val live: ZLayer[Names, Nothing, Teams] =
ZLayer.fromService(TeamsImpl)
}
Teams выберут команду из доступных names, сделав выбор по размеру.
Следуя паттернам использования модулей, хотя для работы pickTeam нужны Names, мы не помещаем его в ZIO[Names, Nothing, Set[String]] — вместо этого мы держим на него ссылку в TeamsImpl.
Наш первый тест прост.
def justTeamsTest = testM("small team test") {
for {
team <- teams.pickTeam(1)
} yield {
assert(team.size)(equalTo(1))
}
}
Чтобы запустить его, нам нужно предоставить ему слой Teams:
suite("needs just Team")(
justTeamsTest
).provideCustomLayer(Names.live >>> Teams.live),
Что такое «>>>»?
Это вертикальная композиция. Она указывает, что нам нужен слой Names, которому нужен слой Teams.
Тем не менее, при запуске этого, есть небольшая проблема.
created namesImpl
created namesImpl
[32m+[0m individually
[32m+[0m needs just Team
[32m+[0m small team test
[36mRan 1 test in 225 ms: 1 succeeded, 0 ignored, 0 failed[0m
Возвращаясь к определению NamesImpl
case class NamesImpl(random: Random.Service) extends Names.Service {
println(s"created namesImpl")
def randomName =
random.nextInt(firstNames.size).map(firstNames(_))
}
Таким образом, наш NamesImpl создается дважды. Чем это чревато, если наш сервис содержит какой-нибудь уникальный системный ресурс приложения? На самом деле, оказывается, что проблема вовсе не в механизме Layers — слои запоминаются и не создаются несколько раз в графе зависимостей. На самом деле это артефакт тестовой среды.
Изменим наш набор тестов на:
suite("needs just Team")(
justTeamsTest
).provideCustomLayerShared(Names.live >>> Teams.live),
Это исправляет проблему, что означает, что слой создается в тесте только один раз
JustTeamsTest требует только teams. Но что, если я хотел получить доступ к Teams и Names?
def inMyTeam = testM("combines names and teams") {
for {
name <- names.randomName
team <- teams.pickTeam(5)
_ = if (team.contains(name)) println("one of mine")
else println("not mine")
} yield assertCompletes
}
Чтобы это работало, нам нужно предоставить и то, и другое:
suite("needs Names and Teams")(
inMyTeam
).provideCustomLayer(Names.live ++ (Names.live >>> Teams.live)),
Здесь мы используем комбинатор ++ для создания слоя Names с Teams. Обратите внимание на приоритет оператора и дополнительные скобки
(Names.live >>> Teams.live)
Вначале, я сам на это попался — в противном случае компилятор будет делать не правильно.
History
History (История) немного сложнее.
object History {
trait Service {
def wonLastYear(team: Set[String]): Boolean
}
case class HistoryImpl(lastYearsWinners: Set[String]) extends Service {
def wonLastYear(team: Set[String]) = lastYearsWinners == team
}
val live: ZLayer[Teams, Nothing, History] = ZLayer.fromServiceM { teams =>
teams.pickTeam(5).map(nt => HistoryImpl(nt))
}
}
Конструктор HistoryImpl требует множество Names. Но единственный способ получить такое — извлечь его из Teams. И для этого требуется ZIO — поэтому мы используем ZLayer.fromServiceM, чтобы он дал нам то, что нам нужно.
Тест проводится по той же схеме, что и раньше:
def wonLastYear = testM("won last year") {
for {
team <- teams.pickTeams(5)
ly <- history.wonLastYear(team)
} yield assertCompletes
}
suite("needs History and Teams")(
wonLastYear
).provideCustomLayerShared((Names.live >>> Teams.live) ++ (Names.live >>> Teams.live >>> History.live))
И все.
Throwable ошибки
В приведенном выше коде предполагается, что вы возвращаете ZLayer[R, Nothing, T] — другими словами, конструкция службы среды имеет тип Nothing. Но если он выполняет что-то вроде чтения из файла или базы данных, то, скорее всего, это будет ZLayer[R, Throwable, T] — потому что такого рода вещи часто включают именно тот внешний фактор, который вызывает исключение. Так что представьте себе, что в конструкции Names произошла ошибка. Для ваших тестов есть способ обойти это:
val live: ZLayer[Random, Throwable, Names] = ???
затем в конце теста
.provideCustomLayer(Names.live).mapError(TestFailure.test)
mapError превращает объект throwable в сбой теста — это то, что вам нужно — он может сказать, что тестовый файл не существует или что-то вроде этого.
Больше ZEnv кейсов
В «стандартные» элементы среды входят Clock (часы) и Random. В наших Names мы уже использовали Random. Но что, если мы также хотим, чтобы один из этих элементов еще больше «понизил» наши зависимости? Для этого я создал вторую версию History — History2 — и здесь для создания экземпляра нужен Clock.
object History2 {
trait Service {
def wonLastYear(team: Set[String]): Boolean
}
case class History2Impl(lastYearsWinners: Set[String], lastYear: Long) extends Service {
def wonLastYear(team: Set[String]) = lastYearsWinners == team
}
val live: ZLayer[Clock with Teams, Nothing, History2] = ZLayer.fromEffect {
for {
someTime <- ZIO.accessM[Clock](_.get.nanoTime)
team <- teams.pickTeam(5)
} yield History2Impl(team, someTime)
}
}
Это не очень полезный пример, но важной частью является то, что строка
someTime <- ZIO.accessM[Clock](_.get.nanoTime)
заставляет нас предоставлять часы в нужном месте.
Теперь .provideCustomLayer может добавить наш слой в стек слоев, и он волшебным образом выталкивает Random в Names. Но этого не будет происходить для часов, которые требуются ниже, в History2. Поэтому следующий код НЕ компилируется:
def wonLastYear2 = testM("won last year") {
for {
team <- teams.pickTeam(5)
_ <- history2.wonLastYear(team)
} yield assertCompletes
}
// ...
suite("needs History2 and Teams")(
wonLastYear2
).provideCustomLayerShared((Names.live >>> Teams.live) ++ (Names.live >>> Teams.live >>> History2.live)),
Вместо этого вам нужно предоставить History2.live часы в явном виде, что делается следующим образом:
suite("needs History2 and Teams")(
wonLastYear2
).provideCustomLayerShared((Names.live >>> Teams.live) ++ (((Names.live >>> Teams.live) ++ Clock.any) >>> History2.live))
Clock.any — это функция, которая получает любые часы, доступные сверху. В этом случае это будут тестовые часы, потому что мы не пытались использовать Clock.live.
Исходный код
Полный исходный код (за исключением throwable) приведен ниже:
import zio._
import zio.test._
import zio.random.Random
import Assertion._
import zio._
import zio.test._
import zio.random.Random
import zio.clock.Clock
import Assertion._
object LayerTests extends DefaultRunnableSpec {
type Names = Has[Names.Service]
type Teams = Has[Teams.Service]
type History = Has[History.Service]
type History2 = Has[History2.Service]
val firstNames = Vector( "Ed", "Jane", "Joe", "Linda", "Sue", "Tim", "Tom")
object Names {
trait Service {
def randomName: UIO[String]
}
case class NamesImpl(random: Random.Service) extends Names.Service {
println(s"created namesImpl")
def randomName =
random.nextInt(firstNames.size).map(firstNames(_))
}
val live: ZLayer[Random, Nothing, Names] =
ZLayer.fromService(NamesImpl)
}
object Teams {
trait Service {
def pickTeam(size: Int): UIO[Set[String]]
}
case class TeamsImpl(names: Names.Service) extends Service {
def pickTeam(size: Int) =
ZIO.collectAll(0.until(size).map { _ => names.randomName}).map(_.toSet ) // да, я знаю, что команда может иметь < размер!
}
val live: ZLayer[Names, Nothing, Teams] =
ZLayer.fromService(TeamsImpl)
}
object History {
trait Service {
def wonLastYear(team: Set[String]): Boolean
}
case class HistoryImpl(lastYearsWinners: Set[String]) extends Service {
def wonLastYear(team: Set[String]) = lastYearsWinners == team
}
val live: ZLayer[Teams, Nothing, History] = ZLayer.fromServiceM { teams =>
teams.pickTeam(5).map(nt => HistoryImpl(nt))
}
}
object History2 {
trait Service {
def wonLastYear(team: Set[String]): Boolean
}
case class History2Impl(lastYearsWinners: Set[String], lastYear: Long) extends Service {
def wonLastYear(team: Set[String]) = lastYearsWinners == team
}
val live: ZLayer[Clock with Teams, Nothing, History2] = ZLayer.fromEffect {
for {
someTime <- ZIO.accessM[Clock](_.get.nanoTime)
team <- teams.pickTeam(5)
} yield History2Impl(team, someTime)
}
}
def namesTest = testM("names test") {
for {
name <- names.randomName
} yield {
assert(firstNames.contains(name))(equalTo(true))
}
}
def justTeamsTest = testM("small team test") {
for {
team <- teams.pickTeam(1)
} yield {
assert(team.size)(equalTo(1))
}
}
def inMyTeam = testM("combines names and teams") {
for {
name <- names.randomName
team <- teams.pickTeam(5)
_ = if (team.contains(name)) println("one of mine")
else println("not mine")
} yield assertCompletes
}
def wonLastYear = testM("won last year") {
for {
team <- teams.pickTeam(5)
_ <- history.wonLastYear(team)
} yield assertCompletes
}
def wonLastYear2 = testM("won last year") {
for {
team <- teams.pickTeam(5)
_ <- history2.wonLastYear(team)
} yield assertCompletes
}
val individually = suite("individually")(
suite("needs Names")(
namesTest
).provideCustomLayer(Names.live),
suite("needs just Team")(
justTeamsTest
).provideCustomLayer(Names.live >>> Teams.live),
suite("needs Names and Teams")(
inMyTeam
).provideCustomLayer(Names.live ++ (Names.live >>> Teams.live)),
suite("needs History and Teams")(
wonLastYear
).provideCustomLayerShared((Names.live >>> Teams.live) ++ (Names.live >>> Teams.live >>> History.live)),
suite("needs History2 and Teams")(
wonLastYear2
).provideCustomLayerShared((Names.live >>> Teams.live) ++ (((Names.live >>> Teams.live) ++ Clock.any) >>> History2.live))
)
val altogether = suite("all together")(
suite("needs Names")(
namesTest
),
suite("needs just Team")(
justTeamsTest
),
suite("needs Names and Teams")(
inMyTeam
),
suite("needs History and Teams")(
wonLastYear
),
).provideCustomLayerShared(Names.live ++ (Names.live >>> Teams.live) ++ (Names.live >>> Teams.live >>> History.live))
override def spec = (
individually
)
}
import LayerTests._
package object names {
def randomName = ZIO.accessM[Names](_.get.randomName)
}
package object teams {
def pickTeam(nPicks: Int) = ZIO.accessM[Teams](_.get.pickTeam(nPicks))
}
package object history {
def wonLastYear(team: Set[String]) = ZIO.access[History](_.get.wonLastYear(team))
}
package object history2 {
def wonLastYear(team: Set[String]) = ZIO.access[History2](_.get.wonLastYear(team))
}
Если у вас есть более сложные вопросы, обращайтесь в Discord #zio-users или посетите сайт и документацию zio.
Узнать о курсе подробнее.
===========
Источник:
habr.com
===========
===========
Автор оригинала: https://timpigden.github.io/
===========Похожие новости:
- [ООП, Программирование, Функциональное программирование] SOLID == ООП?
- [Data Mining, Визуализация данных, Natural Language Processing, Программирование, Python] How to find an English teacher. Part 1
- [Kubernetes, Облачные сервисы] Экономим на облачных затратах Kubernetes на AWS (перевод)
- [Python, Программирование] Введение в асинхронное программирование на Python (перевод)
- [Python, Программирование] А вы можете решить эти три (обманчиво) простые задачи на Python? (перевод)
- [Python, Микросервисы, Программирование, Тестирование веб-сервисов] Функциональные тесты в Циан
- [Управление персоналом] Почему так сложно воспринимать критику?
- [JavaScript, jQuery, Программирование, Разработка веб-сайтов] Как я в 15 лет написал свой первый jQuery плагин и как их создавать
- [DIY или Сделай сам, Программирование микроконтроллеров, Производство и разработка электроники, Разработка робототехники, Схемотехника] Запускаем камеру от телефона, или что делать, когда ничего не получается?
- [C, C++, Программирование, Реверс-инжиниринг] IDA Pro: работа с библиотечным кодом (не WinAPI)
Теги для поиска: #_scala, #_programmirovanie (Программирование), #_zio, #_zlayer, #_scala, #_blog_kompanii_otus._onlajnobrazovanie (
Блог компании OTUS. Онлайн-образование
), #_scala, #_programmirovanie (
Программирование
)
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 21-Ноя 17:57
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
В июле OTUS запускает новый курс «Scala-разработчик», в связи с чем мы подготовили для вас перевод полезного материала. Новая функция ZLayer в ZIO 1.0.0-RC18+ является значительным улучшением старого паттерна модулей, что делает добавление новых сервисов намного быстрее и легче. Однако при использовании на практике я обнаружил, что может потребоваться какое-то время, чтобы освоить эту идиому. Ниже приведен аннотированный пример финальной версии моего тестового кода, в котором я рассматриваю ряд вариантов использования. Большое спасибо Адаму Фрейзеру за помощь в оптимизации и облагораживании моей работы. Сервисы преднамеренно упрощены, так что, надеюсь, они будут достаточно понятны для быстрого чтения. Я предполагаю, что у вас есть базовое понимание ZIO тестов, а также вы ознакомились с основной информацией касательно модулей. Весь код запускается в zio тестах и представляет собой один файл. Вот его верхушка: import zio._
import zio.test._ import zio.random.Random import Assertion._ object LayerTests extends DefaultRunnableSpec { type Names = Has[Names.Service] type Teams = Has[Teams.Service] type History = Has[History.Service] val firstNames = Vector( "Ed", "Jane", "Joe", "Linda", "Sue", "Tim", "Tom") Names Итак, мы добрались до нашего первого сервиса — Names (Имена) type Names = Has[Names.Service]
object Names { trait Service { def randomName: UIO[String] } case class NamesImpl(random: Random.Service) extends Names.Service { println(s"created namesImpl") def randomName = random.nextInt(firstNames.size).map(firstNames(_)) } val live: ZLayer[Random, Nothing, Names] = ZLayer.fromService(NamesImpl) } package object names { def randomName = ZIO.accessM[Names](_.get.randomName) } Тут все в рамках типичного модульного паттерна.
В live используется ZLayer.fromService, который определяется как: def fromService[A: Tagged, B: Tagged](f: A => B): ZLayer[Has[A], Nothing, Has[B]
Игнорируя Tagged (это необходимо для работы всего Has/Layers), вы можете видеть, что здесь используется функция f: A => B — которая в данном случае является просто конструктором кейс класса для NamesImpl. Как вы можете видеть, для работы Names требуется Random из среды zio. Вот тест: def namesTest = testM("names test") {
for { name <- names.randomName } yield { assert(firstNames.contains(name))(equalTo(true)) } } Он использует ZIO.accessM для извлечения Names из среды. _.get извлекает сервис. Мы предоставляем Names для теста следующим образом: suite("needs Names")(
namesTest ).provideCustomLayer(Names.live), provideCustomLayer добавляет слой Names в существующую среду. Teams Суть Teams (Команды) заключается в тестировании зависимостей между модулями, которые мы создали. object Teams {
trait Service { def pickTeam(size: Int): UIO[Set[String]] } case class TeamsImpl(names: Names.Service) extends Service { def pickTeam(size: Int) = ZIO.collectAll(0.until(size).map { _ => names.randomName}).map(_.toSet ) // да, я знаю, что команда может иметь < размер! } val live: ZLayer[Names, Nothing, Teams] = ZLayer.fromService(TeamsImpl) } Teams выберут команду из доступных names, сделав выбор по размеру. Следуя паттернам использования модулей, хотя для работы pickTeam нужны Names, мы не помещаем его в ZIO[Names, Nothing, Set[String]] — вместо этого мы держим на него ссылку в TeamsImpl. Наш первый тест прост. def justTeamsTest = testM("small team test") {
for { team <- teams.pickTeam(1) } yield { assert(team.size)(equalTo(1)) } } Чтобы запустить его, нам нужно предоставить ему слой Teams: suite("needs just Team")(
justTeamsTest ).provideCustomLayer(Names.live >>> Teams.live), Что такое «>>>»? Это вертикальная композиция. Она указывает, что нам нужен слой Names, которому нужен слой Teams. Тем не менее, при запуске этого, есть небольшая проблема. created namesImpl
created namesImpl [32m+[0m individually [32m+[0m needs just Team [32m+[0m small team test [36mRan 1 test in 225 ms: 1 succeeded, 0 ignored, 0 failed[0m Возвращаясь к определению NamesImpl case class NamesImpl(random: Random.Service) extends Names.Service {
println(s"created namesImpl") def randomName = random.nextInt(firstNames.size).map(firstNames(_)) } Таким образом, наш NamesImpl создается дважды. Чем это чревато, если наш сервис содержит какой-нибудь уникальный системный ресурс приложения? На самом деле, оказывается, что проблема вовсе не в механизме Layers — слои запоминаются и не создаются несколько раз в графе зависимостей. На самом деле это артефакт тестовой среды. Изменим наш набор тестов на: suite("needs just Team")(
justTeamsTest ).provideCustomLayerShared(Names.live >>> Teams.live), Это исправляет проблему, что означает, что слой создается в тесте только один раз JustTeamsTest требует только teams. Но что, если я хотел получить доступ к Teams и Names? def inMyTeam = testM("combines names and teams") {
for { name <- names.randomName team <- teams.pickTeam(5) _ = if (team.contains(name)) println("one of mine") else println("not mine") } yield assertCompletes } Чтобы это работало, нам нужно предоставить и то, и другое: suite("needs Names and Teams")(
inMyTeam ).provideCustomLayer(Names.live ++ (Names.live >>> Teams.live)), Здесь мы используем комбинатор ++ для создания слоя Names с Teams. Обратите внимание на приоритет оператора и дополнительные скобки (Names.live >>> Teams.live)
Вначале, я сам на это попался — в противном случае компилятор будет делать не правильно. History History (История) немного сложнее. object History {
trait Service { def wonLastYear(team: Set[String]): Boolean } case class HistoryImpl(lastYearsWinners: Set[String]) extends Service { def wonLastYear(team: Set[String]) = lastYearsWinners == team } val live: ZLayer[Teams, Nothing, History] = ZLayer.fromServiceM { teams => teams.pickTeam(5).map(nt => HistoryImpl(nt)) } } Конструктор HistoryImpl требует множество Names. Но единственный способ получить такое — извлечь его из Teams. И для этого требуется ZIO — поэтому мы используем ZLayer.fromServiceM, чтобы он дал нам то, что нам нужно. Тест проводится по той же схеме, что и раньше: def wonLastYear = testM("won last year") {
for { team <- teams.pickTeams(5) ly <- history.wonLastYear(team) } yield assertCompletes } suite("needs History and Teams")( wonLastYear ).provideCustomLayerShared((Names.live >>> Teams.live) ++ (Names.live >>> Teams.live >>> History.live)) И все. Throwable ошибки В приведенном выше коде предполагается, что вы возвращаете ZLayer[R, Nothing, T] — другими словами, конструкция службы среды имеет тип Nothing. Но если он выполняет что-то вроде чтения из файла или базы данных, то, скорее всего, это будет ZLayer[R, Throwable, T] — потому что такого рода вещи часто включают именно тот внешний фактор, который вызывает исключение. Так что представьте себе, что в конструкции Names произошла ошибка. Для ваших тестов есть способ обойти это: val live: ZLayer[Random, Throwable, Names] = ???
затем в конце теста .provideCustomLayer(Names.live).mapError(TestFailure.test)
mapError превращает объект throwable в сбой теста — это то, что вам нужно — он может сказать, что тестовый файл не существует или что-то вроде этого. Больше ZEnv кейсов В «стандартные» элементы среды входят Clock (часы) и Random. В наших Names мы уже использовали Random. Но что, если мы также хотим, чтобы один из этих элементов еще больше «понизил» наши зависимости? Для этого я создал вторую версию History — History2 — и здесь для создания экземпляра нужен Clock. object History2 {
trait Service { def wonLastYear(team: Set[String]): Boolean } case class History2Impl(lastYearsWinners: Set[String], lastYear: Long) extends Service { def wonLastYear(team: Set[String]) = lastYearsWinners == team } val live: ZLayer[Clock with Teams, Nothing, History2] = ZLayer.fromEffect { for { someTime <- ZIO.accessM[Clock](_.get.nanoTime) team <- teams.pickTeam(5) } yield History2Impl(team, someTime) } } Это не очень полезный пример, но важной частью является то, что строка someTime <- ZIO.accessM[Clock](_.get.nanoTime)
заставляет нас предоставлять часы в нужном месте. Теперь .provideCustomLayer может добавить наш слой в стек слоев, и он волшебным образом выталкивает Random в Names. Но этого не будет происходить для часов, которые требуются ниже, в History2. Поэтому следующий код НЕ компилируется: def wonLastYear2 = testM("won last year") {
for { team <- teams.pickTeam(5) _ <- history2.wonLastYear(team) } yield assertCompletes } // ... suite("needs History2 and Teams")( wonLastYear2 ).provideCustomLayerShared((Names.live >>> Teams.live) ++ (Names.live >>> Teams.live >>> History2.live)), Вместо этого вам нужно предоставить History2.live часы в явном виде, что делается следующим образом: suite("needs History2 and Teams")(
wonLastYear2 ).provideCustomLayerShared((Names.live >>> Teams.live) ++ (((Names.live >>> Teams.live) ++ Clock.any) >>> History2.live)) Clock.any — это функция, которая получает любые часы, доступные сверху. В этом случае это будут тестовые часы, потому что мы не пытались использовать Clock.live. Исходный код Полный исходный код (за исключением throwable) приведен ниже: import zio._
import zio.test._ import zio.random.Random import Assertion._ import zio._ import zio.test._ import zio.random.Random import zio.clock.Clock import Assertion._ object LayerTests extends DefaultRunnableSpec { type Names = Has[Names.Service] type Teams = Has[Teams.Service] type History = Has[History.Service] type History2 = Has[History2.Service] val firstNames = Vector( "Ed", "Jane", "Joe", "Linda", "Sue", "Tim", "Tom") object Names { trait Service { def randomName: UIO[String] } case class NamesImpl(random: Random.Service) extends Names.Service { println(s"created namesImpl") def randomName = random.nextInt(firstNames.size).map(firstNames(_)) } val live: ZLayer[Random, Nothing, Names] = ZLayer.fromService(NamesImpl) } object Teams { trait Service { def pickTeam(size: Int): UIO[Set[String]] } case class TeamsImpl(names: Names.Service) extends Service { def pickTeam(size: Int) = ZIO.collectAll(0.until(size).map { _ => names.randomName}).map(_.toSet ) // да, я знаю, что команда может иметь < размер! } val live: ZLayer[Names, Nothing, Teams] = ZLayer.fromService(TeamsImpl) } object History { trait Service { def wonLastYear(team: Set[String]): Boolean } case class HistoryImpl(lastYearsWinners: Set[String]) extends Service { def wonLastYear(team: Set[String]) = lastYearsWinners == team } val live: ZLayer[Teams, Nothing, History] = ZLayer.fromServiceM { teams => teams.pickTeam(5).map(nt => HistoryImpl(nt)) } } object History2 { trait Service { def wonLastYear(team: Set[String]): Boolean } case class History2Impl(lastYearsWinners: Set[String], lastYear: Long) extends Service { def wonLastYear(team: Set[String]) = lastYearsWinners == team } val live: ZLayer[Clock with Teams, Nothing, History2] = ZLayer.fromEffect { for { someTime <- ZIO.accessM[Clock](_.get.nanoTime) team <- teams.pickTeam(5) } yield History2Impl(team, someTime) } } def namesTest = testM("names test") { for { name <- names.randomName } yield { assert(firstNames.contains(name))(equalTo(true)) } } def justTeamsTest = testM("small team test") { for { team <- teams.pickTeam(1) } yield { assert(team.size)(equalTo(1)) } } def inMyTeam = testM("combines names and teams") { for { name <- names.randomName team <- teams.pickTeam(5) _ = if (team.contains(name)) println("one of mine") else println("not mine") } yield assertCompletes } def wonLastYear = testM("won last year") { for { team <- teams.pickTeam(5) _ <- history.wonLastYear(team) } yield assertCompletes } def wonLastYear2 = testM("won last year") { for { team <- teams.pickTeam(5) _ <- history2.wonLastYear(team) } yield assertCompletes } val individually = suite("individually")( suite("needs Names")( namesTest ).provideCustomLayer(Names.live), suite("needs just Team")( justTeamsTest ).provideCustomLayer(Names.live >>> Teams.live), suite("needs Names and Teams")( inMyTeam ).provideCustomLayer(Names.live ++ (Names.live >>> Teams.live)), suite("needs History and Teams")( wonLastYear ).provideCustomLayerShared((Names.live >>> Teams.live) ++ (Names.live >>> Teams.live >>> History.live)), suite("needs History2 and Teams")( wonLastYear2 ).provideCustomLayerShared((Names.live >>> Teams.live) ++ (((Names.live >>> Teams.live) ++ Clock.any) >>> History2.live)) ) val altogether = suite("all together")( suite("needs Names")( namesTest ), suite("needs just Team")( justTeamsTest ), suite("needs Names and Teams")( inMyTeam ), suite("needs History and Teams")( wonLastYear ), ).provideCustomLayerShared(Names.live ++ (Names.live >>> Teams.live) ++ (Names.live >>> Teams.live >>> History.live)) override def spec = ( individually ) } import LayerTests._ package object names { def randomName = ZIO.accessM[Names](_.get.randomName) } package object teams { def pickTeam(nPicks: Int) = ZIO.accessM[Teams](_.get.pickTeam(nPicks)) } package object history { def wonLastYear(team: Set[String]) = ZIO.access[History](_.get.wonLastYear(team)) } package object history2 { def wonLastYear(team: Set[String]) = ZIO.access[History2](_.get.wonLastYear(team)) } Если у вас есть более сложные вопросы, обращайтесь в Discord #zio-users или посетите сайт и документацию zio. Узнать о курсе подробнее. =========== Источник: habr.com =========== =========== Автор оригинала: https://timpigden.github.io/ ===========Похожие новости:
Блог компании OTUS. Онлайн-образование ), #_scala, #_programmirovanie ( Программирование ) |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 21-Ноя 17:57
Часовой пояс: UTC + 5