[详细描述警告]
我正在运行一些黄瓜测试,这些测试必须在定义的服务器中执行 - 例如: a.功能 -> JBoss 服务器 1 |b.功能 -> JBoss 服务2 |c.功能 -> JB1 |等。
为此,我创建了一个假设的执行器服务,如下所示:
final ExecutorService executorService = Executors.newFixedThreadPool(2); //numberOfServers
for (Runnable task : tasks) {
executorService.execute(task);
}
executorService.shutdown();
try {
executorService.awaitTermination(1000, TimeUnit.SECONDS);
} catch (InterruptedException e) {
//doX();
}
我管理如何选择服务器执行的方式是:
在为executorService创建的Runnable类中,我将instanceId作为参数传递给TestNG(XmlTest类),如下所示:
@Override
public void run() {
setupTest().run();
}
private TestNG setupTest() {
TestNG testNG = new TestNG();
XmlSuite xmlSuite = new XmlSuite();
XmlTest xmlTest = new XmlTest(xmlSuite);
xmlTest.setName(//irrelevant);
xmlTest.addParameter("instanceId", String.valueOf(instanceId));
xmlTest.setXmlClasses(..........);
testNG.setXmlSuites(..........);
return testNG;
}
然后,我在扩展TestNgCucumberAdaptor的类中得到了很好的结果:
@BeforeTest
@Parameters({"instanceId"})
public void setInstanceId(@Optional("") String instanceId) {
if (!StringUtils.isEmpty(instanceId)) {
this.instanceId = Integer.valueOf(instanceId);
}
}
在一个@BeforeClass中,我正在用这个 instanceId 填充一个 Pojo,并在另一个类的 threadLocal 属性中设置 Pojo。目前为止,一切都好。
public class CurrentPojoContext {
private static final ThreadLocal<PojoContext> TEST_CONTEXT = new ThreadLocal<PojoContext>();
...
public static PojoContext getContext(){
TEST_CONTEXT.get();
}
现在问题真正开始了 - 我在 3rd 类中使用 Guice(黄瓜 guice),注入这个包含 instanceId 的 pojo 对象。示例如下:
public class Environment {
protected final PojoContext pojoContext;
@Inject
public Environment() {
this.pojoContext = CurrentPojoContext.getContext();
}
public void foo() {
print(pojoContext.instanceId); // output: 1
Another.doSomething(pojoContext);
}
class Another{
public String doSomething(PojoContext p){
print(p.instanceId); // output: 2
}
}
}
在这里,并非每次都像这样输出(1 和 2),但有时,我意识到不同线程的执行会弄乱属性 pojoContext。我知道这有点令人困惑,但我的猜测是 Guice 注入器在这种情况下不是线程安全的 - 这可能是一个很长的机会,但如果其他人猜测,我将不胜感激。
问候
好吧,只是为了给别人提供解决方案,我的解决方案如下:
- 创建一个类来维护一个 Map,其中标识符(唯一且线程安全的标识符)作为键,Guice 注入器作为值;
-
在我的 Guice 注入器的实例化中,我创建了自己的模块
Guice.createInjector(Stage.PRODUCTION, MyOwnModules.SCENARIO, new RandomModule());
对于此模块:
public class MyOwnModules { public static final Module SCENARIO = new ScenarioModule(MyOwnCucumberScopes.SCENARIO); }
此处定义的范围提供以下内容:
public class MyOwnCucumberScopes { public static final ScenarioScope SCENARIO = new ParallelScenarioScope(); }
-
总而言之,线程安全将在 ParallelScenarioScope 中:
public class ParallelScenarioScope implements ScenarioScope { private static final Logger LOGGER = Logger.getLogger(ParallelScenarioScope.class); private final ThreadLocal<Map<Key<?>, Object>> threadLocalMap = new ThreadLocal<Map<Key<?>, Object>>(); @Override public <T> Provider<T> scope(final Key<T> key, final Provider<T> unscoped) { return new Provider<T>() { public T get() { Map<Key<?>, Object> scopedObjects = getScopedObjectMap(key); @SuppressWarnings("unchecked") T current = (T) scopedObjects.get(key); if (current == null && !scopedObjects.containsKey(key)) { current = unscoped.get(); scopedObjects.put(key, current); } return current; } }; } protected <T> Map<Key<?>, Object> getScopedObjectMap(Key<T> key) { Map<Key<?>, Object> map = threadLocalMap.get(); if (map == null) { throw new OutOfScopeException("Cannot access " + key + " outside of a scoping block"); } return map; } @Override public void enterScope() { checkState(threadLocalMap.get() == null, "A scoping block is already in progress"); threadLocalMap.set(new ConcurrentHashMap<Key<?>, Object>()); } @Override public void exitScope() { checkState(threadLocalMap.get() != null, "No scoping block in progress"); threadLocalMap.remove(); } private void checkState(boolean expression, String errorMessage) { if (!expression) { LOGGER.info("M=checkState, Will throw exception: " + errorMessage); throw new IllegalStateException(errorMessage); } } }
现在的问题是要小心@ScenarioScoped,代码将按预期工作。