在haskell中实现生成测试用例的构建器模式

在haskell中实现生成测试用例的构建器模式,haskell,code-generation,Haskell,Code Generation,我需要为用Java编写的应用程序生成cucumber测试用例 测试用例如下所示: Scenario My great test Given the following input """ Code snippet of a DSL """ And the following data | name | type | value | | a | Boolean | true |

我需要为用Java编写的应用程序生成cucumber测试用例

测试用例如下所示:

Scenario My great test
    Given the following input
        """
            Code snippet of a DSL
        """
    And the following data
        | name | type    | value |
        |    a | Boolean |  true |
        |    b | Integer |     5 |
    When I run the evaluation
    Then the result should be "Yay!"
data TestCase = Scenario String DslStatement DataStatement ResultStatement

data DslStatement = Dsl [TopLevelStatement]

data TopLevelStatement =
    StatementTypeA String
  | StatementTypeB String
  | StatementTypeC String SubStatementTypeA [SubStatementTypeB]
  | StatementTypeD String [String]

...
我已经创建了类似于这个结构的数据类型,如语法树以及一个“后端”,它将接受语法树并创建测试用例字符串

数据类型如下所示:

Scenario My great test
    Given the following input
        """
            Code snippet of a DSL
        """
    And the following data
        | name | type    | value |
        |    a | Boolean |  true |
        |    b | Integer |     5 |
    When I run the evaluation
    Then the result should be "Yay!"
data TestCase = Scenario String DslStatement DataStatement ResultStatement

data DslStatement = Dsl [TopLevelStatement]

data TopLevelStatement =
    StatementTypeA String
  | StatementTypeB String
  | StatementTypeC String SubStatementTypeA [SubStatementTypeB]
  | StatementTypeD String [String]

...
等等

现在,我想使用不同的值、类型和内容生成大量的数据结构

我可以编写一些函数来获取必要的参数,并创建一个语法树,其中的值来自插入到它们应该出现的位置的参数。然而,由于测试用例中包含的DSL随时都可能更改(它是以增量方式开发的),因此我必须始终更改创建不同测试用例类型的所有函数,这是很乏味的。此外,测试用例可以基于标准语法树,对于大多数测试用例,该语法树只能在少数地方修改

我现在的想法是创建或多或少类似于Java中具有流畅接口的构建器模式的函数。从标准语法树开始,我创建了修改该语法树的函数,并返回要进一步修改的结果树,如下所示:

withName :: String -> TestCase -> TestCase
withName name (Scenario _ dsl data result) = Scenario name dsl data result

withResult :: ResultStatement -> TestCase -> TestCase
withResult result (Scenario name dsl data _) = Scenario name dsl data result

...
withName "My Test Case" . withResult (Result "Yay!") $ createStandardTestCase
那么我应该可以写这样的东西:

withName :: String -> TestCase -> TestCase
withName name (Scenario _ dsl data result) = Scenario name dsl data result

withResult :: ResultStatement -> TestCase -> TestCase
withResult result (Scenario name dsl data _) = Scenario name dsl data result

...
withName "My Test Case" . withResult (Result "Yay!") $ createStandardTestCase
只要dsl发生变化,就只需修改构建器函数和后端,以适应我的测试用例

这是解决问题的可能/有效方法吗? 有没有更好的办法来创建这样的语法树

谢谢


--Mathias.

流畅的界面模式在Haskell中称为
Endo
。它是一个
Monoid
,因此使用
mconcat
可以获得一些效率,尽管我很少在实践中使用
Endo
,因为它不是一个巨大的收益

使用这样的定义,您将面临的一个挑战是需要默认的一切,毕竟名为“My Test Case”的
本身需要是一个有效的
TestCase
。这可能意味着您的许多类型将是
可能是
s,也可能只是意味着您需要仔细定义您的类型。这可能与标准语法树的概念有关

创建这样一个可扩展AST的全功能方法是使用这些技术。简而言之,您定义一个通用的“sum”类型运算符,然后构建对递归类型的某些组件的和进行操作的函数。通过使用巧妙的默认值,您可以省略许多样板定义,并允许扩展性

这些技术可能对您的类型有用


最后,在讨论像这样的嵌套数据类型时,如果不提出通过(包括所有可能的电池)或(更简单)查看镜头的建议,就很难做到这一点。这样,您就可以对树进行深入检查,双向使用这些树来查看和构建更新
Endo
s。它们也有聪明的通用原则,比如能够同时“关注”树中的多个位置(这是
控件中的
折叠
s和
遍历
s)。这和你的想法接近吗?