12 votes

Accéder au nom du test ScalaTest depuis l'intérieur du test ?

Est-il possible d'accéder au nom du test en cours d'exécution à partir d'un test ScalaTest (et comment le faire) ?

Contexte :

Je teste que mon objet d'accès aux données lance éventuellement un OverQuotaException si un utilisateur crée trop de pages, par exemple. Ces tests sont assez longs à exécuter. Pour me rassurer, j'aimerais imprimer la progression sur stdout - et comme il y a beaucoup de tests, j'aimerais inclure le nom du test dans la sortie, afin de savoir quel test est en cours d'exécution.

(Je n'ai pas trouvé de fonction apparemment pertinente ici : http://www.artima.com/docs-scalatest-2.0.M5/#org.scalatest.FreeSpec )

Exemple :

  "QuotaCharger can" - {
    "charge and decline quota consumers" - {

      "charge a per site IP number (guest user)" in {
         // ... Here, a guest user post very many comments until it's over quota.
         // This takes a little while, and there are many similar tests.

         // ---> Here <--- I'd like to access the string:
         //   "charge a per site IP number (guest user)",
         //  is that possible somehow?
      }

13voto

Bill Venners Points 1361

La façon prévue de procéder est de remplacer withFixture et de capturer les données de test. Dans ce cas d'utilisation, il est préférable de surcharger withFixture dans fixture.FreeSpec afin de pouvoir passer les données de test dans chaque test plutôt que d'utiliser une var. Des informations à ce sujet sont disponibles ici :

http://www.artima.com/docs-scalatest-2.0.M5/org/scalatest/FreeSpec.html#withFixtureNoArgTest

Quand j'ai vu votre question ce matin, j'ai réalisé que ScalaTest devrait avoir un trait qui fait cela, donc j'en ai ajouté un. Il sera dans la 2.0.M6, la prochaine version, mais en attendant vous pouvez utiliser une copie locale. Le voici :

import org.scalatest._

/**
 * Trait that when mixed into a <code>fixture.Suite</code> passes the
 * <code>TestData</code> passed to <code>withFixture</code> as a fixture into each test.
 *
 * @author Bill Venners
 */
trait TestDataFixture { this: fixture.Suite =>

  /**
   * The type of the fixture, which is <code>TestData</code>.
   */
  type FixtureParam = TestData

  /**
   * Invoke the test function, passing to the the test function to itself, because
   * in addition to being the test function, it is the <code>TestData</code> for the test.
   *
   * <p>
   * To enable stacking of traits that define <code>withFixture(NoArgTest)</code>, this method does not
   * invoke the test function directly. Instead, it delegates responsibility for invoking the test function
   * to <code>withFixture(NoArgTest)</code>.
   * </p>
   *
   * @param test the <code>OneArgTest</code> to invoke, passing in the
   *   <code>TestData</code> fixture
   */
  def withFixture(test: OneArgTest) {
    withFixture(test.toNoArgTest(test))
  }
}

Vous l'utiliserez comme suit :

import org.scalatest._

class MySpec extends fixture.FreeSpec with TestDataFixture {
  "this technique" - {
    "should work" in { td =>
      assert(td.name == "this technique should work")
     }
    "should be easy" in { td =>
      assert(td.name == "this technique should be easy")
    }
  }
}

3voto

Cosmia Luna Points 111

Créez votre propre trait, par exemple RichFreeSpec .

trait RichFreeSpec extends Free {
  protected final class RichFreeSpecStringWrapper(name: scala.Predef.String) {
    def in(f: String => scala.Unit) {
      def f2 = f(name)
      new WordSpecStringWrapper(string).in(f2)
    }
  }  

  protected implicit def convertToRichFreeSpecStringWrapper(n: scala.Predef.String): = {
    new RichFreeSpecStringWrapper(n)
  }
}

Il suffit alors de l'utiliser :

"sth" in { testName => 
   ...
 }

Bien entendu, vous pouvez aller plus loin et mettre en œuvre la hiérarchie complète des noms.

3voto

Nerrve Points 1772

Vous pouvez utiliser BeforeAndAfterEachTestData pour ce dont vous avez besoin.

Si vous avez besoin d'accéder au nom du cas de test dans la méthode beforeEach ou afterEach.

class MyTestSuite with AnyFunSuiteLike with BeforeAndAfterEachTestData {

    override def beforeEach(testData: TestData): Unit = {
        testData.name // do whatever.
        super.beforeEach(testData)
    }
}

Si vous avez besoin d'accéder au nom du scénario de test dans le scénario de test lui-même, vous pouvez utiliser une approche locale au niveau des threads

private val currentTestCaseName = new ThreadLocal[String]

override def beforeEach(testData: TestData): Unit = {
    currentTestCaseName.set(testData.name)
    super.beforeEach(testData)
}

test("fancy test") {
    currentTestCaseName.get() // do whatever
}

1voto

KajMagnus Points 2580

Voici une solution. Étendez cette classe au lieu de FreeSpec. Licence : CC0 .

Edit : Cela ne fonctionne pas avec les tests simultanés.

(La différence entre cette approche et l'autre réponse est que 1) ici, il y a une currentTestName et dans l'autre réponse, le nom du test est transmis au corps du test, et 2) ce nom de test inclut tous les noms de branches de test concaténés + le nom de test actuel, alors que le nom de test de l'autre réponse est exactement le nom de test (sans les noms de branches de test).

(Oups, il faut utiliser <code>getOrElse ...</code> au lieu de ma belle <code>getOrDie</code> .)

/**
 * Adds a field `currentTestName` that you can use inside a FreeSpec test,
 * if you for example have many tests that take rather long, and you wonder
 * which one is currently running.
 */
trait RichFreeSpec extends FreeSpec {

  private var _currentTestName: Option[String] = None
  def currentTestName = _currentTestName getOrDie "DwE90RXP2"

  protected override def runTest(testName: String, args: org.scalatest.Args) {
    _currentTestName = Some(testName)
    super.runTest(testName, args)
  }
}

0voto

ldeck Points 117

Si le but est de pouvoir accéder au nom du test depuis n'importe où, comme l'a suggéré @kajmanus dans les commentaires précédents, un ThreadLocal convient parfaitement.

Vous pourriez définir une classe de cas pour stocker les informations dont vous avez besoin pour le contexte de test actuel, par exemple,

case class TestContext(name: Option[String] = None)

object TestContext {
  val currentTest: ThreadLocal[TestContext] =
    ThreadLocal.withInitial(() => TestContext())
}

Définissez ensuite un trait que vos différentes spécifications étendront, par exemple,

trait BaseFunSpec
  extends AnyFunSpec
  ...
{
  override protected def withFixture(test: NoArgTest): Outcome = {
    try {
      TestContext.currentTest.set(TestContext(name = Some(test.name)))
      super.withFixture(test)
    } finally {
      TestContext.currentTest.remove()
    }
  }
}

Enfin, vous pouvez accéder au contexte de test actuel que vous avez défini pour la discussion en cours (qui, dans cet exemple, est purement le nom du test) à partir de n'importe quel endroit de la discussion en cours,

def cachedResults(bytes: Array[Byte], fileType: String): Unit = {
  TestContext.currentTest.get().name match {
    case Some(testname) => 
      import scala.util.Using
      val file = new File("target", s"${testname}.${fileType}")
      Using(new BufferedOutputStream(new FileOutputStream(file))) { os =>
        os.write(bytes)
      }
    case None => throw new IllegalStateException("Unknown test context")
  }
}

Cela fonctionnera que vous exécutiez des tests en parallèle ou non, à condition que vous ne traitiez pas les choses de manière asynchrone (c'est-à-dire dans un autre thread).

Une utilisation plus propre de cette méthode consiste à créer des acteurs ciblés, par exemple,

case class TestContext(name: Option[String] = None)

object TestContext {
  val currentTest: ThreadLocal[TestContext] = ThreadLocal.withInitial(() => TestContext())

  class TestNamer {
    def currentName: String = currentTest.get().name match {
      case Some(testname) => testname
      case None => throw new IllegalStateException("No test context available")
    }
  }

  class TestContextWriter(testNamer: TestNamer = new TestNamer()) {
    def cachedBytes(bytes: Array[Byte], extension: String): Array[Byte] = {
      import java.io.{BufferedOutputStream, File, FileOutputStream}
      import scala.util.Using

      val file = new File("target", s"${testNamer.currentName}.${extension}")

      Using(new BufferedOutputStream(new FileOutputStream(file))) { outstream =>
        outstream.write(bytes)
      }

      bytes
    }
  }
}

Et faire des injections si nécessaire :

trait BaseFunSpec {
  val testContextWriter = new TestContextWriter()

  def fetchRawResults(...): Array[Byte] = {
    ...
    testContextWriter.cachedBytes(bytes, "pdf")
  }
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X