मैं ZIO और ZIO टेस्ट में नया हूं और मैं ZIO v1.0.0RC17 के तहत लिखी गई शेड्यूलिंग सेवा का परीक्षण करना चाहता हूं:

सेवाएं):

import zio.{RIO, Schedule}
import zio.clock.Clock
import zio.duration._

trait ModuleA {
  def moduleA: ModuleA.Service
}

object ModuleA {
  trait Service {
    def schedule(arg: Int): RIO[Clock, Unit]
  }
}

trait ModuleALive extends ModuleA {

  def moduleB: ModuleB.Service

  override def moduleA: ModuleA.Service = new ModuleA.Service {
    override def schedule(arg: Int): RIO[Clock, Unit] = {
      moduleB.run(arg).repeat(Schedule.spaced(1 day)).map(_ => ())
    }
  }
}

trait ModuleB {
  def moduleB: ModuleB.Service
}

object ModuleB {
  trait Service {
    def run(arg: Int): RIO[Clock, Unit]
  }
}

मॉड्यूलए की सेवा को मूल रूप से मॉड्यूलए.सर्विस.रन में दिए गए तर्क के साथ दिन में एक बार मॉड्यूलबी की सेवा पद्धति को चलाना चाहिए।

मैं जो परीक्षण लिखना चाहता हूं:

import java.util.concurrent.atomic.AtomicInteger

import zio.clock.Clock
import zio.duration._
import zio.test.environment.TestClock
import zio.test.{DefaultRunnableSpec, assertCompletes, suite, testM}
import zio.{RIO, Task, ZIO}

object ExampleSpec extends DefaultRunnableSpec(ExampleSuite.suite1)

object ExampleSuite {

  val counter: AtomicInteger = new AtomicInteger(0)

  trait ModuleBTest extends ModuleB {
    override def moduleB: ModuleB.Service = new ModuleB.Service {
      override def run(arg: Int): RIO[Clock, Unit] = ZIO.effectTotal(counter.incrementAndGet())
    }
  }

  object ModuleATest extends ModuleALive with ModuleBTest

  def verifyExpectedInvocationCount(expectedInvocationCount: Int): Task[Unit] = {
    val actualInvocations = counter.get()
    if (counter.get() == expectedInvocationCount)
      ZIO.succeed(())
    else
      throw new Exception(s"expected invocation count: $expectedInvocationCount but was $actualInvocations")
  }

  val suite1 = suite("a")(
    testM("a should correctly schedule b") {
      for {
        _ <- ModuleATest.moduleA.schedule(42).fork
        _ <- TestClock.adjust(12 hours)
        _ <- verifyExpectedInvocationCount(1)
        _ <- TestClock.adjust(12 hours)
        _ <- verifyExpectedInvocationCount(2)
      } yield assertCompletes
    }
  )
}

मैंने काउंटर का उपयोग करके परीक्षण को सरल बना दिया, वास्तव में मैं आमंत्रण गणना के साथ-साथ सही तर्क को सत्यापित करने के लिए मॉकिटो का उपयोग करना चाहता हूं। हालाँकि, यह परीक्षण काम नहीं करता है। मेरी समझ में यह https में वर्णित टाइमिंग ओवरहेड द्वारा शुरू की गई दौड़ की स्थिति के कारण है: //zio.dev/docs/howto/howto_test_effects#testing-clock

अब, एक वादा का उपयोग करके इस समस्या से निपटने के तरीके के उदाहरण हैं। मैंने कोशिश की कि काउंटर को इस तरह से एक वादे के साथ बदल दिया जाए:

import java.util.concurrent.atomic.AtomicInteger

import zio.test.{DefaultRunnableSpec, assertCompletes, suite, testM}
import zio.{Promise, Task, UIO, ZIO}

object ExampleSpec extends DefaultRunnableSpec(ExampleSuite.suite1)

object ExampleSuite {

  val counter: AtomicInteger = new AtomicInteger(0)
  var promise: UIO[Promise[Unit, Int]] = Promise.make[Unit, Int]

  trait ModuleBTest extends ModuleB {
    override def moduleB: ModuleB.Service = new ModuleB.Service {
      override def run(arg: Int) = promise.map(_.succeed(counter.incrementAndGet))
    }
  }

  object ModuleATest extends ModuleALive with ModuleBTest

  def verifyExpectedInvocationCount(expectedInvocationCount: Int, actualInvocations: Int): Task[Unit] = {
    if (actualInvocations == expectedInvocationCount)
      ZIO.succeed(())
    else
      throw new Exception(s"expected invocation count: $expectedInvocationCount but was $actualInvocations")
  }

  val suite1 = suite("a")(
    testM("a should correctly schedule b") {
      for {
        _ <- ModuleATest.moduleA.schedule(42).fork
        p <- promise
        actualInvocationCount <- p.await
        _ <- verifyExpectedInvocationCount(expectedInvocationCount = 1, actualInvocationCount)
      } yield assertCompletes
    }
  )
}

इसके इस्तेमाल से टेस्ट खत्म नहीं होगा। हालांकि, मुझे पूरा यकीन है कि मैं वादे का गलत इस्तेमाल कर रहा हूं।

इस परीक्षण परिदृश्य को कोई सही तरीके से कैसे देखेगा?

1
imRentable 29 मार्च 2020, 13:22

1 उत्तर

सबसे बढ़िया उत्तर

आपके उदाहरण में promise का प्रकार UIO[Promise[Unit, Int]] है, इसलिए आप हर बार एक नया वादा बना रहे हैं। परिणामस्वरूप, आपका प्रभाव जिस वादे को पूरा कर रहा है, वह उस वादे से भिन्न है जिस पर आपका परीक्षण प्रतीक्षा कर रहा है, जिसके परिणामस्वरूप रद्दीकरण हो जाता है।

इसका परीक्षण करने के लिए, आप ऐसा कुछ कर सकते हैं:

import zio.clock.Clock
import zio.duration._
import zio.test.environment.TestClock
import zio.test.{ assertCompletes, suite, testM, DefaultRunnableSpec }
import zio._

object ExampleSpec extends DefaultRunnableSpec {

  trait ModuleA {
    def moduleA: ModuleA.Service
  }

  object ModuleA {
    trait Service {
      def schedule(arg: Int): RIO[Clock, Unit]
    }
  }

  trait ModuleALive extends ModuleA {

    def moduleB: ModuleB.Service

    override def moduleA: ModuleA.Service = new ModuleA.Service {
      override def schedule(arg: Int): RIO[Clock, Unit] =
        moduleB.run(arg).repeat(Schedule.spaced(1.day)).map(_ => ())
    }
  }

  trait ModuleB {
    def moduleB: ModuleB.Service
  }

  object ModuleB {
    trait Service {
      def run(arg: Int): RIO[Clock, Unit]
    }
  }

  trait ModuleBTest extends ModuleB {
    val counter: Ref[Int]
    val invocations: Queue[Int]
    override def moduleB: ModuleB.Service = new ModuleB.Service {
      override def run(arg: Int): UIO[Unit] =
        counter.updateAndGet(_ + 1).flatMap(invocations.offer).unit
    }
  }

  object ModuleATest {
    def apply(ref: Ref[Int], queue: Queue[Int]): ModuleALive with ModuleBTest =
      new ModuleALive with ModuleBTest {
        val counter     = ref
        val invocations = queue
      }
  }

  def verifyExpectedInvocationCount(invocations: Queue[Int], expected: Int): Task[Unit] =
    invocations.take.flatMap { actual =>
      if (actual == expected)
        ZIO.succeed(())
      else
        ZIO.fail(new Exception(s"expected invocation count: $expected but was $actual"))
    }

  def spec = suite("a")(
    testM("a should correctly schedule b") {
      for {
        counter     <- Ref.make(0)
        invocations <- Queue.unbounded[Int]
        moduleATest = ModuleATest(counter, invocations)
        _           <- moduleATest.moduleA.schedule(42).fork
        _           <- TestClock.adjust(12.hours)
        _           <- verifyExpectedInvocationCount(invocations, 1)
        _           <- TestClock.adjust(12.hours)
        _           <- verifyExpectedInvocationCount(invocations, 2)
      } yield assertCompletes
    }
  )
}

चूंकि हम कई प्रभावों के पूरा होने की प्रतीक्षा करना चाहते हैं, इसलिए मैं उनके समन्वय के लिए Queue का उपयोग करता हूं। ध्यान देने योग्य कुछ अन्य बातें:

  • बेहतर त्रुटि रिपोर्टिंग प्राप्त करने के लिए आप verifyExpectedInvocationsCount विधि को ZIO टेस्ट में अभिकथन के उपयोग से बदल सकते हैं।
  • यह उदाहरण पुराने परिवेश एन्कोडिंग का उपयोग करता है। परतों के साथ इन सेवाओं की रचना करना और उनमें से किसी एक के लिए परीक्षण कार्यान्वयन में स्वैप करना काफी आसान होगा।
  • यदि आप परीक्षण करना चाहते हैं कि कोई प्रभाव समाप्त नहीं होता है (उदाहरण के लिए, यदि आप पर्याप्त समय तक प्रतीक्षा नहीं करते हैं तो कोई अन्य मान कतार में नहीं रखा जाता है) तो आप TestAspect#nonTermination का उपयोग कर सकते हैं।
1
Adam Fraser 30 मार्च 2020, 14:53