scalatestでより構造的なbeforeを書きたい

# 問題

RSpecのデフォルト挙動のようにscalatestのFunSpecで各describeレベルごとにbeforeを行いたい。

```scala
class MathTest extends FunSpecEx with Matchers {

  var subject: Int = _

  describeWithBefore("0で初期化すれば"){ subject = 0 } {
    it("0になる") {
      subject should be(0)
    }

    describeWithBefore("5足せば"){ subject += 5 } {
      it("5になる") {
        subject should be(5)
      }

      describeWithBefore("さらに3引けば"){ subject -= 3 } {
        it("2になる") {
          subject should be(2)
        }
        it("その結果は偶数である") {
          (2 % 2) should be (0)
        }
      }

      describeWithBefore("その上で-1掛けると"){ subject *= -1 } {
        it("-5になる") {
          subject should be(-5)
        }
      }
    }
  }

}
```

しかし、scalatestの[before(fixture)はdescribeのレベルと対応付けるような機能はない](http://www.scalatest.org/user_guide/sharing_fixtures)。

# 解決策

上のように書くためのクラスを作ったのでこれを使う。

```scala

import org.scalatest._
import org.scalactic._

import scalaz._
import Scalaz._

class FunSpecEx extends FunSpec {

  private[this] type Description = (String, () => Unit)
  private[this] type DescriptionOrTestTitle = \/[Description, String]
  private[this] var tree: Tree[DescriptionOrTestTitle] = ("", () => ()).left[String].node()

  override protected val it: ItWord = new ItWord() {
    override def apply(specText: String, testTags: Tag*)(testFun: => Any /* Assertion */)(implicit pos: source.Position): Unit = {
      tree = tree.loc.insertDownLast(\/-(specText).asInstanceOf[DescriptionOrTestTitle].leaf).toTree
      super.apply(specText, testTags: _*)(testFun)(pos)
    }
  }

  protected[this] def describeWithBefore(description: String)
    (before: => Unit)
    (fun: => Unit)
    (implicit pos: org.scalactic.source.Position): Unit = {
    val backup = tree
    tree = (description, before _).left[String].node()
    super.describe(description)(fun)(pos)
    tree = backup.loc.insertDownLast(tree).toTree
  }

  protected override def runTest(testName: String, args: Args): Status = {
    val targetTestLeaf: TreeLoc[DescriptionOrTestTitle] = tree.loc.find { treeLoc =>
      val strOfDescriptionAndTest = treeLoc.path.reverse.map {
        case -\/(yyy) => yyy._1
        case \/-(xxx) => xxx
      }
      strOfDescriptionAndTest.tail.mkString(" ") == testName
    }.get
    val descriptions: Seq[DescriptionOrTestTitle] = targetTestLeaf.path.reverse
    val befores: Seq[() => Unit] = descriptions.map {
      case -\/(yyy) => yyy._2
      case \/-(_) => () => ()
    }
    for (before <- befores) before()
    super.runTest(testName, args)
  }

}
```

出力はこんな感じ

```
[info] Compiling 1 Scala source to /home/kbigwheel/code/_sandbox/2018-09-17-scalatest-structure/target/scala-2.12/test-classes...
[info] FunSpecEx:
[info] CubeCalculatorTest:
[info] 0で初期化すれば
[info] - 0になる
[info]   5足せば
[info]   - 5になる
[info]     さらに3引けば
[info]     - 2になる
[info]     - その結果は偶数である
[info]     その上で-1掛けると
[info]     - -5になる
[info] Run completed in 333 milliseconds.
[info] Total number of tests run: 5
[info] Suites: completed 2, aborted 0
[info] Tests: succeeded 5, failed 0, canceled 0, ignored 0, pending 0
[info] All tests passed.
```

# 蛇足

これが必要 = テスト対象がmutableということなので、scalaの理想的には邪道。
ただしテスト対象にDB状態が絡んでいたり、アプリケーション全体をテストする場合にはimmutableにはほぼできないのでこれが役に立ちます。

ある程度こなれたらライブラリ化もしくはscalatest本体へPR作ろうと思っています。

## ライブラリ化しました

https://search.maven.org/artifact/com.github.bigwheel/scalatest-structured-before_2.12/1.0/jar