Проблема синхронизации в тесте Android JUnit при наблюдении за объектом, измененным другим потоком - PullRequest
2 голосов
/ 15 января 2012

Мое приложение для Android должно выполнять следующие действия: MainActivity запускает другой поток в начале, называемый UdpListener, который может принимать вызовы UDP от удаленного сервера. Если он получает пакет с содержимым «ОБНОВЛЕНИЕ», UdpListener должен уведомить MainActivity, чтобы что-то сделать.

(В реальном приложении вариант использования выглядит так, что мое приложение прослушивает на удаленном сервере. Если на удаленном сервере есть какие-либо новые данные, оно уведомляет каждого клиента (приложение) по UDP, чтобы клиент знал что он может загрузить новые данные с помощью HTTP).

Я пытался смоделировать это в тесте JUnit. Тест содержит внутренний класс, который проверяет MainActivity, а также отправляет UDP-вызов UdpListener:

public class UdpListener extends Thread implements Subject {
    private DatagramSocket serverSocket;
    private DatagramPacket receivedPacket;
    private boolean running = false;
    private String sentence = "";

    private Observer observer;

    private static final String TAG = "UdpListener";

    public UdpListener(Observer o) throws SocketException {
        serverSocket = new DatagramSocket(9800);
        setRunning(true);

        observer = o;
    }

    @Override
    public void run() {
        setName(TAG);
        while (isRunning())  {
            byte[] receivedData = new byte[1024];
            receivedPacket = new DatagramPacket(receivedData, receivedData.length);
            try {
                serverSocket.receive(receivedPacket);
            } 
            catch (IOException e) {
                Log.w(TAG, e.getMessage());
            }

            try {
                sentence = new String(receivedPacket.getData(), 0, receivedPacket.getLength(), "UTF-8");
                if ("UPDATE".equals(sentence))  {
                    notifyObserver();
                }
            } 
            catch (UnsupportedEncodingException e) {
                Log.w(TAG, e.getMessage());
            }
        }
    }

    private boolean isRunning() {
        return running;
    }

    public void setRunning(boolean running) {
        this.running = running;
    }

    @Override
    public void notifyObserver() {
        observer.update();
    }
}

Это соответствующий тест:

@RunWith(RobolectricTestRunner.class)
public class UdpListenerTest {

    private MainActivityMock mainActivityMock = new MainActivityMock();

    @Before
    public void setUp() throws Exception {
        mainActivityMock.setUpdate(false);
    }

    @After
    public void tearDown() throws Exception {
        mainActivityMock.setUpdate(false);
    }

    @Test
    public void canNotifyObserver() throws IOException, InterruptedException {
        UdpListener udpListener = new UdpListener(mainActivityMock);
        udpListener.setRunning(true);
        udpListener.start();    

        InetAddress ipAddress = InetAddress.getByName("localhost");
        DatagramSocket datagramSocket = new DatagramSocket();
        DatagramPacket sendPacket = new DatagramPacket("UPDATE".getBytes(), "UPDATE".length(), ipAddress, 9800);
        datagramSocket.send(sendPacket);
        datagramSocket.close();

        assertTrue(mainActivityMock.isUpdate());

        udpListener.setRunning(false);
    }

    private class MainActivityMock implements Observer {

        private boolean update = false;

        @Override
        public void update() {
            update = true;
        }

        public boolean isUpdate()  {
            return update;
        }

        public void setUpdate(boolean update)  {
            this.update = update;
        }
    }
}

Хорошо, что моя концепция работает. Но этот тест не делает. Это означает, что это происходит только тогда, когда я останавливаюсь с точкой останова на этой строке datagramSocket.close(); и жду около секунды. Почему это происходит, понятно. Но как я могу сделать это автоматически? Я думал об использовании wait (), но для этого мне нужно вызвать notify () из другого потока. Та же проблема с CountDownLatch. Я не уверен, как решить эту проблему без изменения UdpListener.

Ответы [ 2 ]

0 голосов
/ 15 января 2012

Одним из решений будет использование очереди блокировки размером 1 для хранения полученных результатов.

Запрос на isUpdate (который будет принимать элемент из очереди блокировки) будет блокироваться до тех пор, пока пакет обновления (или любой другой пакет) не будет помещен в очередь.

Если вы хотите, чтобы все ваши звонки были неблокирующими, вы можете использовать Future для получения своего результата. Future.get () заблокирует пока ваш результат не получен.

0 голосов
/ 15 января 2012

Вы можете написать простой цикл с указанным тайм-аутом.

try {
  long timeout = 500; // ms
  long lastTime = System.currentTimeMillis();
  while(timeout > 0 && !mainActivityMock.isUpdate()) {
    Thread.sleep(timeout);
    timeout -= System.currentTimeMillis() - lastTime;
    lastTime = System.currentTimeMillis();
  }
} catch(InterruptedException e) {

} finally {
  assertTrue(mainActivityMock.isUpdate());
}

Кстати, вы должны объявить свой атрибут running как volatile.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...