How to cut a long ScalaTest specification into pieces

I am testing a REST API and the code is as follows:

  • Material customization, populating the database with PUSH calls
  • API Testing
  • Testing API b ...

Currently, the code is in one pretty huge FlatSpec :

 class RestAPITest extends FlatSpec with Matchers with ScalatestRouteTest with SprayJsonSupport 

I would like to slice the parts of the "Testing API a / b / ..." so that the code is more manageable. Trying to do this seems like a no-no: what type of it is - how to convey it, etc. Etc.

So what is the recommended way to do such things.

The a / b / ... tests can be run in parallel as soon as the basic setup is successful.

I am currently using assume in a / b / ... tests to cancel them if initialization fails.

Should I look at the "fixtures" or what for this? BeforeAndAfterAll tried BeforeAndAfterAll before, but actually it didn’t help me.

Thanks for the pointers / opinions. How do you keep your test kits short?

+1
scala functional-testing scalatest spray
Dec 03 '14 at 13:21
source share
4 answers

Adding as a new answer, so the differences are clear and the discussion above does not need to be deleted. If I didn’t make any typos, this should work (I checked it and accepted it in my project).

 import org.scalatest._ /* * Mix this trait into any specs that need 'TestA' to have been run first. */ trait TestAFirst { // Reading a 'TestA' object field causes it to be instantiated and 'TestA' to be executed (but just once). // val testASuccess = TestA.success } /* * 'TestA' gets instantiated via the companion object explicitly (thus @DoNotDiscover) * and creates a success value field. Otherwise, it a test just like any other. */ @DoNotDiscover class TestA private extends FlatSpec { private var success = false // read once, by the companion object behavior of "Root class"; { it should "run prior to any of the B,C classes" in { assert(true) // ... A tests success = true } } } object TestA { val success = { val o= new TestA o.execute o.success // getting a value from the executed test ('.execute()' itself doesn't provide a status) } } class TestB extends FlatSpec with TestAFirst { behavior of "class B"; { it should "run after A has been run" in { assume(testASuccess) assert(true) // ... B tests } } } class TestC extends FlatSpec with TestAFirst { behavior of "class C"; { it should "run after A has been run" in { assume(testASuccess) assert(true) // ... C tests } } } 
0
Dec 07 '14 at 20:07
source share

I would say that mixing in BeforeAndAfter or BeforeAndAfterAll is one of the most intuitive ways to reduce duplication in a scenario where you want: "Settings" β†’ "run test1" β†’ "Settings" β†’ "run test2", "Setup" will be (basically ) same.

Suppose we have a nasty, difficult Database test:

 object Database { private var content: List[Int] = Nil def add(value: Int) = content = value :: content def remove(): Unit = content = if (content.nonEmpty) content.tail else Nil def delete(): Unit = content = Nil def get: Option[Int] = content.headOption override def toString = content.toString() } 

This is a singleton (therefore, we cannot just create a new Database for each test) and its mutable (therefore, if the first test changes something, this will affect the second test).

Obviously, it would be more desirable to have such a structure (for example, it would be much better to work with the List that implements Database in this example), but suppose we cannot just change this structure,

Change Please note that in this case it is impossible (at least I cannot think of a way) to run tests that mutate the same singleton instance in parallel.

To still be able to test it, we must have a clean state before running each test. Assuming we want to populate the database with the same values ​​for each test, we could let our testuite base class extend BeforeAndAfter . Note There are two features: BeforeAndAfter , which defines before and after , which are executed before and after each test case , and BeforeAndAfterAll , which differs in that it defines methods that are executed before and after each set of tests .

 class RestAPITest extends FlatSpec with ShouldMatchers with BeforeAndAfter { before { Database.delete() Database.add(4) Database.add(2) } } 

Now we can have the ATest test suite expand this base class:

 class ATest extends RestAPITest { "The database" should "not be empty" in { Database.get shouldBe defined } it should "contain at least two entries" in { Database.remove() Database.get shouldBe defined } it should "contain at most two entries" in { Database.remove() Database.remove() Database.get should not be defined } } 

At the beginning of each test, the database contains two values, 4 and 2 . Now we can use other tests for this base class:

 class BTest extends RestAPITest { "The contents of the database" should "add up to 6" in { getAll.sum shouldBe 6 } "After adding seven, the contents of the database" should "add up to 13" in { Database.add(7) getAll.sum shouldBe 13 } def getAll: List[Int] = { var result: List[Int] = Nil var next = Database.get while(next.isDefined){ result = next.get :: result Database.remove() next = Database.get } result } } 

Of course, we can also share the common functionality in ordinary methods, as is done in getAll , which is used by both test cases.

Addendum :

Quote from the question:

How do you keep your test packages short?

The test code in my opinion is not much different from the production code. Perform common functions with the methods and put them in separate traits if they do not belong to a specific class that you already have.

However, if your production code requires that the tests always run the same piece of code, there may be too many dependencies in your production code. Say you have a function (in your production code)

 def plus: Int = { val x = Database.get.get Database.remove() x + Database.get.get } 

then you cannot check this function if you did not populate the database with the two values ​​you want to add. The best way to keep your tests short and readable in this case is to reorganize your production code.

 "plus 3 2" should "be 5" in { Database.add(3) Database.add(2) plus shouldBe 5 } 

can be

 "plus 3 2" should "be 5" in { plus(3,2) shouldBe 5 } 

In some cases, it is not easy to get rid of dependencies. But you may want your objects in a test script to depend on a special test environment. A database is a great example for this, as is a file system, or logging. These things are usually more expensive to execute (I / O access) and may have additional dependencies that you must first install.

In these cases, your tests are likely to profit from using mock objects . For example, you may need to implement a database in memory that implements the interface of your database.

+2
Dec 03 '14 at 15:44
source share

How I got the job is below.

I invoke tests B and C to execute A in front of them, by mixing in the TestAFirst . This feature also ensures that TestA will run only once.

There are several options. I decided to prevent TestA starting automatically by annotating DoNotDiscover . Ideally, I would like TestA look as normal a test as possible by clicking all the dependency processing on TestAFirst .

 import java.util.concurrent.atomic.{AtomicBoolean} import org.scalatest.{DoNotDiscover, FlatSpec} /* * Mix this trait into any specs that need 'TestA' to have been run first. */ trait TestAFirst extends FlatSpec { import TestAFirst._ if (!doneTestA.getAndSet(true)) { // tbd. Can we detect here if 'execute' failed? Would be a better place to set 'testASuccess' than within the // 'TestA' itself (= limit all dependency things to 'TestAFirst'). // (new TestA).execute } } object TestAFirst { val doneTestA= new AtomicBoolean @volatile var testASuccess= false // remains 'false' if 'TestA' failed, causing B and C to cancel } /* * 'TestA' is a test *almost* like any other. */ @DoNotDiscover class TestA extends FlatSpec { import TestAFirst._ behavior of "Root class"; { it should "run prior to any of the B,C classes" in { assert(true) // ... A tests testASuccess = true } } } class TestB extends TestAFirst { import TestAFirst._ behavior of "class B"; { it should "run after A has been run" in { assume(testASuccess) assert(true) // ... B tests } } } class TestC extends TestAFirst { import TestAFirst._ behavior of "class C"; { it should "run after A has been run" in { assume(testASuccess) assert(true) // ... C tests } } } 

Better solutions are still welcome, but since this works, I would like to post it. There are also other threads in SO ( Doing something before or after all the Scalatest and org.scalatest tests: global setup (e.g. beforeAllSuites?) ) That deal with similar problems, but not with a clear answer.

Naturally, the idea is to host TestB , TestC , etc. in different source files, reaching the modularity that I was aiming for. This is just a fragment.

+1
Dec 05 '14 at 12:55
source share

Do you use the Spray framework? You can try spray.testkit.Specs2RouteTest

  class RestAPISpec extends Specification with Specs2RouteTest { "RestAPITest" should { "Test A" in { ... some code } "Test B" in { ... some code } } } 
-one
Dec 03 '14 at 14:13
source share



All Articles