Как прекратить сочинять примеры для юнит-тестов и начать жить.
$dashboardGenerator->generate($user, $options);
expected == actual
WAT? == actual
А что, если проверками сравнивать не с примером... А просто проверять какие-нибудь свойства объекта?
Но для этого надо сначала определиться с требованиями.
А теперь для каждого требования нам надо придумать тестовые примеры.
И реализовать все это!
Для любого пользователя, у которого нет детей, Ассистент должен содержать 0 элементов.
Для любого пользователя, у которого есть дети, Ассистент должен содержать не больше N элементов.
Для любого пользователя, Ассистент должен содержать еще не выполненные задания.
Случайная генерация тестовых примеров порождает тесты, которые могут иногда упасть, а иногда - пройти.
Запусти тесты сто раз!
property("Contains only elements for my children") =
forAll { user: User =>
val elements: Seq[Element] = Assistant.generate(user).elements
elements forall { element =>
user.children contains element.child
}
}
property("MUST NOT contain completed tasks") =
forAll { user: User =>
val elements: Seq[Element] = Assistant.generate(user).elements
elements
.find(element => element.task.isCompleted(user, element.child))
.isEmpty
}
Допустим, у нас есть алгоритм реализации кучи. Этот алгоритм поддерживает простые операции с кучей: insert, isEmpty, meld, findMin и deleteMin.
trait Heap {
type H // type of a heap
type A // type of an element
def ord: Ordering[A] // ordering on elements
def empty: H // creates empty heap
def isEmpty(h: H): Boolean // whether the given heap h is empty
def insert(x: A, h: H): H // the heap resulting from inserting x into h
def meld(h1: H, h2: H): H // the heap resulting from merging h1 and h2
def findMin(h: H): A // a minimum of the heap h
def deleteMin(h: H): H // a heap resulting from deleting a minimum of h
}
trait IntHeap extends Heap {
override type A: Int
override def ord = scala.math.Ordering.Int
}
В общем-то не важно, как это работает, важно, что оно определяет тип H:
object BinomialHeap extends Heap with IntHeap {
type Rank = Int
case class Node(x: A, r: Rank, c: List[Node])
override type H = List[Node]
}
property("empty") = forAll { (a: Int) =>
val heap = insert(a, empty)
findMin(heap) == a
}
property("isEmpty on empty returns true") = forAll { (a: Int) =>
isEmpty(empty)
}
property("findMin") = forAll { (a: Int, b: Int) =>
val heap = insert(a, insert(b, empty))
(a min b) == findMin(heap)
}
property("deleteMin") = forAll { (a: Int) =>
val heap = insert(a, empty)
empty == deleteMin(heap)
}
property("meld contains all items of both heaps") =
forAll { (heap1: H, heap2: H) =>
val elementsInBothHeaps = heapToList(heap1) ++ heapToList(heap2)
elementsInBothHeaps.sorted == heapToList(meld(heap1, heap2))
}
[error] /.../test/scala/HeapSpecification.scala:46:
could not find implicit value for parameter a1:
org.scalacheck.Arbitrary[example.BinomialHeap.H]
[error] property("meld contains all items of both heaps") =
forAll { (heap1: H, heap2: H) =>
Нам нужна случайная куча, но компилятор не знает, как ее создать или где взять.
Создадим генератор?
lazy val genHeap: Gen[H] = for {
// get random value of type A
randomValue <- arbitrary[A]
// choose empty heap or random generated one
randomHeap <- oneOf(const(empty), genHeap)
} yield insert(randomValue, randomHeap)
implicit lazy val arbitraryHeap: Arbitrary[H] = Arbitrary(genHeap)
И самая большая боль property-based testing тоже. Написать хороший генератор бывает очень сложно. Особенно для более «живых» примеров, чем небольшая структура.
Это жесть же!
Зато такой генератор подойдет для тестирования почти любой части приложения.