如何应用同步?

时间:2012-01-15 19:46:59

标签: java synchronization

我有UI自动化测试。测试涉及三个实体 -

数据对象类 - 要填写表单的数据。在此,页面上的每个表单都可以由不同的数据对象表示 帮助程序类 - 在页面上填写表单中的数据 测试类 - 使用数据对象和助手类来执行测试。

以下是测试的缩减版本 -

public class ParallelDataObject {

    HelperClass helperClass = new HelperClass();
    Data data;

    @BeforeMethod
    public void setTestData() {
        data = new Data();
        helperClass.setData(data);
    }

    @Test
    public void passM1() {
        helperClass.verifyFlag();
    }

    @Test
    public void failM2() {
        data.setFlag(false);
        helperClass.setData(data);
        helperClass.verifyFlag();
    }

    @Test
    public void passM3() {
        helperClass.verifyFlag();
    }

    @Test
    public void failM4() {
        data.setFlag(false);
        helperClass.setData(data);
        helperClass.verifyFlag();
    }
}

class HelperClass {
    Data data;  

    public void setData(Data data) {
        synchronized (data) {
            this.data = data;
        }
    }

    public void verifyFlag() {
        synchronized (data) {
            assert data.getFlag();
        }
    }
}

class Data {
    private boolean flag;

    public Data() {
        flag = true;
    }

    public Data setFlag(boolean flag) {
        synchronized (this) {
            this.flag = flag;
            return this;
        }
    }

    public boolean getFlag() {
        synchronized (this) {
            return flag;
        }
    }

当并行执行方法时,我遇到了奇怪的结果,因为数据不是线程安全的。然后我合并了同步块,但我遇到了奇怪的结果。 我确信我已经搞砸了如何在这里使用同步。任何见解?

我又做了一次运动。我设置了另一个与第一个测试类完全相同的Test类。我从helper和data类中删除了所有同步。当我并行运行类(而不是方法)。测试结果如预期。当我并行执行类时,为什么不运行并发,即使它们使用相同的辅助类和数据对象?

2 个答案:

答案 0 :(得分:2)

HelperClassData是线程安全的。

问题是你的一些测试方法会执行几个操作。并且测试方法中的操作序列不是原子的,只要它不同步即可。

例如,在failM4执行期间,helperClass的状态可能会被其他线程修改。

我建议您不要在测试方法之间使用共享状态,因为同步会使并发测试执行的优势无效。

答案 1 :(得分:1)

考虑使用ThreadLocal。这样每个线程都有自己的HelperClass副本。请注意,同步单独的方法不会给你任何东西 - 在一个测试中(在一个线程中)所做的更改可被其他测试看到

class ParallelDataObject {

    private final ThreadLocal<HelperClass> helperClassThreadLocal = new ThreadLocal<HelperClass>() {

        @Override
        protected HelperClass initialValue() {
            return new HelperClass(new Data());
        }
    };

    private HelperClass helperClass() {
        return helperClassThreadLocal.get();
    }

    @Test
    public void passM1() {
        helperClass().verifyFlag();
    }

    @Test
    public void failM2() {
        helperClass().getData().setFlag(false);
        helperClass().verifyFlag();
    }

}

class HelperClass {

    private final Data data;

    public HelperClass(Data data) {
        this.data = data;
    }

    public Data getData() {
        return data;
    }

    public void verifyFlag() {
        assert data.getFlag();
    }
}

class Data {
    private boolean flag = true;

    public Data setFlag(boolean flag) {
        this.flag = flag;
        return this;
    }

    public boolean getFlag() {
        return flag;
    }
}

其他改进:

  • passM3failM4是多余的
  • 因为HelperClass需要Data的实例才能工作,所以它应该使用构造函数依赖项声明它
  • 使用时:

    synchronized(this)
    

    包装整个方法体,考虑在方法声明中使用synchronized关键字(更易读)。

  • {li>

    ThreadLocal s

    不再需要同步

测试无状态

@gpeche 提出了一个很好的建议,即测试应该是独立的。不幸的是(为什么,为什么!?)JUnit重复使用相同的测试用例类实例(在这种情况下为ParallelDataObject)来执行所有测试方法。这意味着将任何有状态对象分配给测试用例类字段是危险的,必须避免。

在这种特殊情况下, OP必须在每个测试方法中创建一个新的HelperClass实例(事实上,这并不是一个坏主意)

class ParallelDataObject {

    @Test
    public void passM1() {
        final HelperClass helperClass = new HelperClass(new Data());

        helperClass.verifyFlag();
    }

    @Test
    public void failM2() {
        final Data data = new Data();
        data.setFlag(false);

        final HelperClass helperClass = new HelperClass(data);

        helperClass.verifyFlag();
    }

}