Одним из подходов к тестированию на основе моделей является создание хорошо известной реализации и сравнение с ней.Одна из лучших стратегий для этого - использовать язык более высокого уровня, чтобы краткая и лаконичная реализация была легко читаемой и аргументированной.
Я имел большой успех, используя Haskell и QuickCheck , чтобы выполнить тестирование оценки запроса на основе модели.Я представлял язык запросов как тип данных Haskell, а затем просто сделал его экземпляром Arbitrary
.Как только это было сделано, QuickCheck мог генерировать бесконечное количество запросов, которые я мог затем оценить как по тестируемому серверу, так и по реализации моей модели.
Scala и Scala-Check может позволить вам сделать то же самое на JVM.
В качестве быстрого примера того, как это можно использовать, приведем быстрый и грязный пример:
import Test.QuickCheck
import Control.Monad (liftM,liftM2)
-- An simple expression data type.
-- Expression is either (Add X Y, Minus X Y, Multiply X Y or just a plain value)
-- where X and Y are further expressions
data Expression = Add Expression Expression
| Minus Expression Expression
| Multiply Expression Expression
| Value Int
deriving Show
-- This is my model-based approach, high level
evaluate :: Expression -> Int
evaluate (Value x) = x
evaluate (Add lhs rhs) = evaluate lhs + evaluate rhs
evaluate (Minus lhs rhs) = evaluate lhs - evaluate rhs
evaluate (Multiply lhs rhs) = evaluate lhs * evaluate rhs
ПослеЭто хорошо известная реализация простой модели.Просто проверяя код, я могу быть уверен, что он делает то, что, как я думаю, должен делать.Теперь я могу использовать Arbitrary
для генерации произвольно сложных выражений.Приведенные ниже весовые коэффициенты показывают частоту (таким образом, 70 процентов сгенерированных выражений будут Value
, а 10 процентов будут произвольными Add
операторами и т. Д.)
instance Arbitrary Expression where
arbitrary = frequency [
(70,liftM Value arbitrary)
, (10,liftM2 Add arbitrary arbitrary)
, (10,liftM2 Minus arbitrary arbitrary)
, (10,liftM2 Multiply arbitrary arbitrary)
]
Это определение дает QuickCheck возможность создаватьмне произвольные выражения.Например:
> sample (arbitrary :: Gen Expression)
Multiply (Value 1) (Value 0)
Value 1
Add (Value (-3)) (Add (Value (-1)) (Minus (Value 3) (Value 0)))
Value (-4)
Value (-19)
Add (Value (-49)) (Value (-55))
Minus (Value 50) (Value 103)
Value 210
Value (-172)
Value (-907)
Value (-2703)
Я думаю, что это уже довольно круто (но, может быть, я просто странный).По-настоящему мощным моментом является то, что теперь я могу указать инвариант, который хранится над всеми сгенерированными значениями
prop_testAgainstModel :: Expression -> Bool
prop_testAgainstModel expr = evaluate expr == evaluate' expr
Где мы предполагаем, что evaluate'
- это мой сверхсложный способ оценки вещей, которые я хочу проверить по моиммодель.QuickCheck затем сгенерирует множество произвольных выражений и попытается опровергнуть мое утверждение.
Вот пример, который приведен против одной из моих попыток создать более сложную функцию оценки.
> quickCheck prop_testAgainstModel
*** Failed! Falsifiable (after 28 tests):
Multiply (Value (-53758)) (Value 125360)
Я использовал этоПодход к тестированию внешнего сервера написан на C ++.Реализация модели на Haskell состояла, может быть, из нескольких сотен строк кода, и я мог бы сравнить это с результатами веб-сервиса с не намного большим количеством кода, чем указано выше.