Как GebSpec скрывает поле _browser и для чего подходит $ spock_sharedField__browser? - PullRequest
0 голосов
/ 06 января 2019

Я пытаюсь понять, как Геб и Спок работают внутри, чтобы понять, что на самом деле происходит в моих тестах.

Я обнаружил, что GebSpec, который я расширяю для написания своих тестов, имеет поле Browser _browser.

Я также обнаружил, что GebSpec имеет метод getBrowser(), который возвращает _browser, поэтому к _browser можно получить доступ через getBrowser() и get_browser(). Но интересно то, что при отладке в расширении intelliJ экземпляр GebSpec не показывает поле _browser, а только поле $spock_sharedField__browser.

Небольшой пример:

Отладка моего класса: в экземпляре GebSpec есть поле spock_sharedField__browser, но нет поля _browser

Как им удается скрыть от меня поле _browser в отладчике и почему они это делают?

Напомним, поле Браузер _browser объявлено в GebSpec, а поле $spock_sharedField__browser - нет.

Там также нет метода get$spock_sharedField__browser(), но я все еще могу получить доступ и манипулировать $spock_sharedField__browser.

Я пытался сделать это сам:

Я написал класс TestClass, который объявляет _browser точно аналогом GebSpec, но если я отлаживаю здесь, поле _browser отображается нормально, как и следовало ожидать

Может кто-нибудь объяснить мне, что происходит?

Зачем скрывать _browser?

Для чего $spock_sharedField__browser хорош?

ОБНОВЛЕНИЕ: Я думаю, что следующий код описывает это довольно хорошо:

import geb.spock.GebSpec
class GebHomeSpec extends GebSpec{
    def "test Geb homepage"(){
        when:
        ['get$spock_sharedField__browser', 'getBrowser', 'get_browser'].each {
            try {
                println this."${it}"()
            } catch (MissingFieldException e) {
                println e
            }
        }
        ['$spock_sharedField__browser', 'browser', '_browser'].each {
            try {
                println this.getMetaClass().getAttribute(this, it)
            } catch (MissingFieldException e){
                println e
            }
        }

        then:
        true
    }
}

Результат на консоли:

null
geb.Browser@352ff4da
geb.Browser@352ff4da
null
groovy.lang.MissingFieldException: No such field: browser for class: GebHomeSpec
groovy.lang.MissingFieldException: No such field: _browser for class: GebHomeSpec

Моя интерпретация, учитывая ответ kriegaex, заключается в том, что в Компиляции во время преобразования Спока объявляется поле $ spock_sharedField__browser, а поле _browser удаляется. Полевой браузер никогда не существовал. Но есть еще геттеры для браузера и _browser. Интересно, откуда там их данные (в данном случае geb.Browser@352ff4da), так как ни одно из полей больше не существует, как показывают исключения. По крайней мере, она соответствует информации отладки (см. Первое изображение / ссылку), которая показывает поле $ spock_sharedField__browser, но не поле _browser и не браузер полей.

Наконец, я заметил (и я действительно не знаю, как это объяснить), геттеры для _browser и browser больше недоступны за пределами класса (см. Ниже). Я думал, что концепция приватности не реализована в Groovy, и в любом случае создание приватности для пользователей не имеет смысла.

import geb.spock.GebSpec
class Main {
    static void main(String[] args) {
        GebSpec gebSpec = new GebSpec()
        ['get$spock_sharedField__browser', 'getBrowser', 'get_browser'].each {
            try {
                println gebSpec."${it}"()
            } catch (MissingFieldException e) {
                println e
            }
        }
        ['$spock_sharedField__browser', 'browser', '_browser'].each {
            try {
                println gebSpec.getMetaClass().getAttribute(gebSpec, it)
            } catch (MissingFieldException e){
                println e
            }
        }
    }
}

Это приводит к

null
groovy.lang.MissingFieldException: No such field: $spock_sharedField__browser for class: org.codehaus.groovy.runtime.NullObject
groovy.lang.MissingFieldException: No such field: $spock_sharedField__browser for class: org.codehaus.groovy.runtime.NullObject
null
groovy.lang.MissingFieldException: No such field: browser for class: geb.spock.GebSpec
groovy.lang.MissingFieldException: No such field: _browser for class: geb.spock.GebSpec

В целом я нахожу это довольно запутанным, и мне интересно, для чего это хорошо. Зачем вводить $ spock_sharedField__browser и удалять _browser?

1 Ответ

0 голосов
/ 06 января 2019

Если вы используете IntelliJ IDEA, вы можете просто декомпилировать класс GebSpec и увидеть что-то вроде этого (это то, что на самом деле создал компилятор Groovy при компиляции класса библиотеки):

public class GebSpec extends Specification implements GroovyObject {
  // ...

  @Shared
  @FieldMetadata(
    line = 29,
    name = "_browser",
    ordinal = 2
  )
  protected volatile Browser $spock_sharedField__browser;

  // ...

  public Browser createBrowser() {
    CallSite[] var1 = $getCallSiteArray();
    return !__$stMC && !BytecodeInterface8.disabledStandardMetaClass() ? (Browser)ScriptBytecodeAdapter.castToType(var1[8].callConstructor(Browser.class, this.createConf()), Browser.class) : (Browser)ScriptBytecodeAdapter.castToType(var1[6].callConstructor(Browser.class, var1[7].callCurrent(this)), Browser.class);
  }

  public Browser getBrowser() {
    CallSite[] var1 = $getCallSiteArray();
    if (BytecodeInterface8.isOrigZ() && !__$stMC && !BytecodeInterface8.disabledStandardMetaClass()) {
      if (ScriptBytecodeAdapter.compareEqual(var1[11].callGroovyObjectGetProperty(this), (Object)null)) {
        Browser var3 = this.createBrowser();
        ScriptBytecodeAdapter.setGroovyObjectProperty(var3, GebSpec.class, this, (String)"_browser");
      }
    } else if (ScriptBytecodeAdapter.compareEqual(var1[9].callGroovyObjectGetProperty(this), (Object)null)) {
      Object var2 = var1[10].callCurrent(this);
      ScriptBytecodeAdapter.setGroovyObjectProperty((Browser)ScriptBytecodeAdapter.castToType(var2, Browser.class), GebSpec.class, this, (String)"_browser");
    }

    return (Browser)ScriptBytecodeAdapter.castToType(var1[12].callGroovyObjectGetProperty(this), Browser.class);
  }

  // ...

  public Browser get$spock_sharedField__browser() {
    return this.$spock_sharedField__browser;
  }

  public void set$spock_sharedField__browser(Browser var1) {
    this.$spock_sharedField__browser = var1;
  }
}

Я думаю, что вы уже погрузились достаточно глубоко, чтобы понять без дальнейших объяснений.


Обновление: Я забыл упомянуть: ваш тестовый класс не наследует GebSpec (который снова наследуется от Specification, т. Е. Код не будет преобразован Spock / Geb, поскольку он имеет неправильный базовый класс. Если вы сделаете это, хотя:

package de.scrum_master.stackoverflow

import geb.spock.GebSpec
import spock.lang.Shared

class FooIT extends GebSpec {
  @Shared
  def myField

  def test() {
    expect:
    true
  }
}

Тогда декомпилированный код будет:

package de.scrum_master.stackoverflow;

import geb.spock.GebSpec;
import org.codehaus.groovy.runtime.ScriptBytecodeAdapter;
import org.codehaus.groovy.runtime.callsite.CallSite;
import org.spockframework.runtime.ErrorCollector;
import org.spockframework.runtime.SpockRuntime;
import org.spockframework.runtime.ValueRecorder;
import org.spockframework.runtime.model.BlockKind;
import org.spockframework.runtime.model.BlockMetadata;
import org.spockframework.runtime.model.FeatureMetadata;
import org.spockframework.runtime.model.FieldMetadata;
import org.spockframework.runtime.model.SpecMetadata;
import spock.lang.Shared;

@SpecMetadata(
  filename = "FooIT.groovy",
  line = 6
)
public class FooIT extends GebSpec {
  @Shared
  @FieldMetadata(
    line = 7,
    name = "myField",
    ordinal = 0
  )
  protected volatile Object $spock_sharedField_myField;

  public FooIT() {
    CallSite[] var1 = $getCallSiteArray();
  }

  @FeatureMetadata(
    line = 10,
    name = "test",
    ordinal = 0,
    blocks = {@BlockMetadata(
  kind = BlockKind.EXPECT,
  texts = {}
)},
    parameterNames = {}
  )
  public void $spock_feature_1_0() {
    CallSite[] var1 = $getCallSiteArray();
    ErrorCollector $spock_errorCollector = (ErrorCollector)ScriptBytecodeAdapter.castToType(var1[2].callConstructor(ErrorCollector.class, false), ErrorCollector.class);
    ValueRecorder $spock_valueRecorder = (ValueRecorder)ScriptBytecodeAdapter.castToType(var1[3].callConstructor(ValueRecorder.class), ValueRecorder.class);

    Object var10000;
    try {
      try {
        SpockRuntime.verifyCondition($spock_errorCollector, $spock_valueRecorder.reset(), "true", Integer.valueOf(12), Integer.valueOf(5), (Object)null, $spock_valueRecorder.record($spock_valueRecorder.startRecordingValue(Integer.valueOf(0)), true));
        var10000 = null;
      } catch (Throwable var13) {
        SpockRuntime.conditionFailedWithException($spock_errorCollector, $spock_valueRecorder, "true", Integer.valueOf(12), Integer.valueOf(5), (Object)null, var13);
        var10000 = null;
      } finally {
        ;
      }

      var1[4].call(var1[5].call(this.getSpecificationContext()));
    } finally {
      $spock_errorCollector.validateCollectedErrors();
      var10000 = null;
    }

  }

  public Object get$spock_sharedField_myField() {
    return this.$spock_sharedField_myField;
  }

  public void set$spock_sharedField_myField(Object var1) {
    this.$spock_sharedField_myField = var1;
  }
}

Обновление 2:

Что касается ваших дополнительных вопросов, я могу только догадываться об ответах, я уверен, что пользователям нравятся @ erdi (Geb сопровождающий), @ Szymon Stepniak , @ Leonard Brünings (которые кажутся Groovy-трещинами, которыми я не являюсь) мог бы сказать об этом больше, но OTOH, это не дискуссионный форум, и вопросы не особенно хорошо подходят для SO. Во всяком случае, я отредактировал теги вопросов, добавив в них "groovy", чтобы привлечь их внимание.

Зачем вводить $spock_sharedField__browser и удалять _browser?

Я думаю, что это всего лишь результат того, как Спок преобразует аннотацию @Shared в переменную-член с помощью named, чтобы очень маловероятно, чтобы она конфликтовала с любыми существующими именами членов. Вы также видите, что это происходит в декомпилированной версии моей собственной спецификации Spock / Geb.

Но есть еще геттеры для browser и _browser.

Конечно, есть геттер для browser, так как в Geb DLS вы обычно не смотрите за кулисы, а просто используете синтаксический сахар browser для доступа к экземпляру браузера. Этот Groovy-изм будет называться getBrowser(), как вы, наверное, знаете. Этот конкретный метод получения объявлен явно в классе GebSpec, чтобы сделать член удобным для доступа (вы также видите здесь ленивую логику создания браузера):

Browser getBrowser() {
    if (_browser == null) {
        _browser = createBrowser()
    }
    _browser
}

Интересно, откуда берутся их данные (в данном случае geb.Browser@352ff4da), так как ни одно из полей больше не существует, как показывают исключения.

Я недостаточно знаю о возможностях динамического языка Groovy, чтобы ответить на этот вопрос, но вы можете увидеть реальную механику в моих фрагментах декомпилированного кода.

Доступ к специфичным для Спока членам класса из-за пределов работающей спецификации, очевидно, не работает и, вероятно, не предназначен. Но если вы запустите этот тест, он работает просто отлично:

package de.scrum_master.stackoverflow

import geb.spock.GebSpec
import spock.lang.Shared

class FooIT extends GebSpec {
  @Shared
  def myField = "foo"

  def test() {
    given:
    println browser
    println myField
    expect:
    true
  }
}

Журнал консоли:

geb.Browser@1722011b
foo
...