在 groovy spock 框架中更新地图

Update a Map in groovy spock framework

我有以下 spock 规范,想根据数据 table 更新地图。有人可以帮助实现这个吗

def "groovy map update"() {                                                   
        setup: "step1"                                                            

        Map json = [                                                              
                user :[                                                           
                        name : 'ABC'                                              
                ]]                                                                

        when: "step2"                                                             

        println ('Before modification:')                                          
        println (json)                                                            

        then: "step3"                                                             

        json.with {                                                               
            //user.name = value // this one works                                 
            (field) = value   // this one does not work                           
        }                                                                         

        println ('After modification:')                                           
        println (json)                                                            

        where:                                                                    
        field                               | value                               
        'user.name'                         | 'XYZ'                               

    }   

then 部分用于断言而不是更新等。因此您必须在 when 部分更新映射,然后在 then 部分测试结果.例如像这样:

def "groovy map update"() {
    setup: 'create json'
    Map json = [user: [name: 'ABC']]

    when: 'update it'
    def target = json
    for (node in path - path.last()) {
        target = target[node]
    }
    target[path.last()] = value

    then: 'check the assignment'
    json.user.name == value

    where:
    path             | value
    ['user', 'name'] | 'XYZ'
}

更新嵌套 Map 值的一种方法是使用路径节点列表而不是字段符号,然后遍历它们以获得最后一个 Map 实例并在那里设置值:

def target = json
for (node in path - path.last()) {
    target = target[node]
}
target[path.last()] = value

接受的解决方案是正确的,我只是想展示一个以稍微不同的方式做同样事情的替代方案,假设你想在你的 where: 块中坚持使用 field 的点分符号.我刚刚添加了两个测试用例以确保它按预期工作。

@Unroll
def "set #field to #value"() {
  setup: 'create json'
  Map json = [user: [name: 'ABC', address: [street: '21 Main St', zip: '12345', city: 'Hometown']]]

  when: 'update it'
  def subMap = json
  field.split("[.]").each {
    if (subMap[it] instanceof Map)
      subMap = subMap[it]
    else
      subMap[it] = value
  }
  println json

  then: 'check the assignment'
  json.newField == value ||
    json.user.name == value ||
    json.user.address.zip == value

  where:
  field              | value
  'newField'         | 'dummy'
  'user.name'        | 'XYZ'
  'user.address.zip' | '98765'
}

更新: 如果您想节省几行代码,您还可以通过 inject(..) 使用折叠(或减少或累积)操作,如所述 here

@Unroll
def "set #field to #value"() {
  setup: 'create json'
  Map json = [user: [name: 'ABC', address: [street: '21 Main St', zip: '12345', city: 'Hometown']]]

  when: 'update it'
  field.split("[.]").inject(json) { subMap, key ->
    subMap[key] instanceof Map ? subMap[key] : subMap.put(key, value)
  }
  println json

  then: 'check the assignment'
  json.newField == value ||
    json.user.name == value ||
    json.user.address.zip == value

  where:
  field              | value
  'newField'         | 'dummy'
  'user.name'        | 'XYZ'
  'user.address.zip' | '98765'
}

您是否觉得可读性可能取决于您对函数式编程等主题的熟悉程度,尤其是 map/reduce。除了简洁之外,这里的魅力在于我们不再需要闭包之外的局部变量,而我们只需将迭代 n 的结果注入(因此称为方法名称)到迭代 n+1。

顺便说一句,作为一个很好的副作用 inject(..) 因为我在这里使用它 returns 您设置或覆盖的值的先前值。只需在 field.split("[.]").inject(json) ... 前添加 println 即可看到。


更新 2: 请注意,由于 instanceof Map 检查我的代码中的启发式方法。 IE。这两种情况都行不通:

    'user.address'            | [street: '23 Test Blvd', zip: '33333', city: 'Somewhere']
    'user.address'            | '23 Test Blvd, 33333 Somewhere'

不过,这个可以工作,因为没有预先存在的值:

    'user.alternativeAddress' | [street: '23 Test Blvd', zip: '33333', city: 'Somewhere']