Событийное тестирование для Android - PullRequest
1 голос
/ 10 августа 2011

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

Я ищу способ для тестирования приложения с помощью событий, которые можно перенести в среду тестирования.Если мое тестируемое приложение может «сообщать» мои тесты, когда происходит ожидаемое событие, это было бы очень полезно.В прошлом я использовал Event Tracing для Windows для Windows \ Windows Phone с большим эффектом.

Плохой способ сделать это, на что я смотрю, - это пройти тестПриложение читает LogCat в реальном времени и уведомляет тест, когда происходит ожидаемое событие.

У кого-нибудь есть другие идеи?

1 Ответ

1 голос
/ 17 августа 2011

Для решения этой проблемы я использую logcat ОС.Я проверяю части кода, которые тестируются, с помощью сообщений трассировки и жду, пока они появятся в тестовом коде.Это, пожалуй, не самый эффективный способ ведения дел, но он соответствует моим потребностям.Сначала я попытался создать асинхронную задачу, которая читала бы из logcat, но столкнулась с проблемами синхронизации.Наконец-то я реализовал что-то вроде этого:

    logger.info("Click on something");
    EmSingleton.get().setCheckPoint();
    solo.clickOnText(SOMETHING, 1, true); // writes RELOAD_COMPLETE to logcat when done

    logger.info("Validate that the event is done);
    EmSingleton.get().waitForEvent(GrouponLnConstants.TodaysDeal.RELOAD_COMPLETE, 15000);
    // Do other stuff... 

Код EventManager (который я завернул в синглтон под названием EmSingleton):

public class EventManager {
private ArrayList<String> args = null;
private long startTime;

public EventManager() {
    args = new ArrayList<String>(Arrays.asList("-v", "time", "-d"));
    setCheckPoint();
}

public EventManager(ArrayList<String> _args) {
    this();
    for(String s: args) {
        if (!args.contains(s)) {
            args.add(s);
        }
    }
}

public void setCheckPoint()
{
    Time startTimeOS = new android.text.format.Time();
    startTimeOS.setToNow();
    startTime = startTimeOS.toMillis(true);
}

public LogEvent checkEvent(String filter) throws Exception {
    ArrayList<LogEvent> events = gatherEvents();

    for(LogEvent event: events){
        if(event.checkMsgSubstring(filter)){
            return event;
        }
    }

    return null;
}

public LogEvent waitForEvent(String filter, int timeout) throws Exception
{
    int retries = timeout/1000 == 0 ? 1 : timeout/1000;
    for(int i = 0; i < retries; i++)
    {
        LogEvent event = checkEvent(filter);
        if(event != null){
            return event;
        }
        Thread.sleep(1000);
    }
    return null;
}

public ArrayList<LogEvent> getEvents() {
    return gatherEvents();
}

public void clearEvents() throws Exception{
    ArrayList<String> commandLine = new ArrayList<String>();
    commandLine.add("logcat");
    commandLine.add("-c");

    ProcessBuilder pb = new ProcessBuilder(commandLine.toArray(new String[0]));
    pb.redirectErrorStream(true);
    Process logcatProcess = pb.start();

    logcatProcess.waitFor();
}

protected ArrayList<LogEvent> gatherEvents() {
    ArrayList<LogEvent> events = new ArrayList<LogEvent>();
    final StringBuilder log = new StringBuilder();
    BufferedReader br;
    try {
        ArrayList<String> commandLine = new ArrayList<String>();
        commandLine.add("logcat");
        if (null != args) {
            commandLine.addAll(args);
        }

        ProcessBuilder pb = new ProcessBuilder(commandLine.toArray(new String[0]));
        pb.redirectErrorStream(true);
        Process logcatProcess = pb.start();

        InputStream is = logcatProcess.getInputStream();
        InputStreamReader isr = new InputStreamReader(is);
        br = new BufferedReader(isr);

        String line = null;

        while (true) {
            line = br.readLine();
            if(line == null){
                break;
            }
            // Add events to the events arraylist if they occur after the LogCollector has started to run
            LogEvent event = new LogEvent(line);
            if(event.peekTimeMS() > startTime)
            {
                events.add(event);
            }
        }
    } catch (Exception e) {
        TestLogger.get().error("GrouponTest", String.format("gatherEvents doInBackground failed: %s", e.toString()));
    }

    return events;
}
}

Вам потребуется установить следующее разрешение в своем приложении:

<uses-permission android:name="android.permission.READ_LOGS">

Я также создал объект LogEvent для анализа событий, поступающих от logcat:

public class LogEvent {
String orignalString = null;
boolean parsed = false;

long timeMs = 0;
String timeString = null;
String verbosity = null;
String tag = null;
int pid = 0;
String msg = null;

// Lazy factory design pattern
// http://en.wikipedia.org/wiki/Lazy_initialization
public LogEvent(String s) {
    orignalString = s;
}

public long getTimeMs() {
    checkInit();
    return timeMs;
}

public long peekTimeMS()
{
    // Time is always formattted as such: MM-DD HH:MM:SS.XXX
    return parseTime(orignalString.substring(0,5), orignalString.substring(6,18));
}

public String getTimeString() {
    checkInit();
    return timeString;
}

public String getVerbosity() {
    checkInit();
    return verbosity;
}

public String getTag() {
    checkInit();
    return tag;
}

 public int getPid() {
    checkInit();
    return pid;
}

public String getMsg() {
    checkInit();
    return msg;
}

/**
 * Checks to see if the event message contains a substring
 * @param check
 * @return
 */
public Boolean checkMsgSubstring(String check)
{
    int index = orignalString.indexOf(check);
    boolean isSubstring = (index >= 0);
    return isSubstring;
}

public String toString()
{
    checkInit();
    return String.format("%s %s/%s(%d): %s", timeString, verbosity, tag, pid, msg);
}

private void checkInit()
{
    if(!parsed)
    {
        parseEvent();
        parsed = true;
    }
}

private void parseEvent()
{
    try{
    String [] splits = orignalString.split("[ ]+");
    // Sometimes the PID is of format ( XX) instead of (XX)
    if(splits[2].indexOf(")") < 0)
    {
        splits[2] += splits[3];
        ArrayList<String> formatted = new ArrayList<String>(Arrays.asList(splits));
        formatted.remove(3);
        splits = formatted.toArray(new String[formatted.size()]);
    }

    // Parse time
    timeMs = parseTime(splits[0], splits[1]);
    timeString = String.format("%s %s", splits[0], splits[1]);
    // Parse tag
    verbosity = parseVerbosity(splits[2]);
    tag = parseTag(splits[2]);
    pid = parsePid(splits[2]);
    // Parse message (may be empty)
    if (splits.length > 3) {
        msg = orignalString.substring(orignalString.indexOf(splits[3]));
    } else {
        msg = "";
    }
    }
    catch (Exception e)
    {
        // TODO: there are some strangely formated events in the system. need to deal with these?
    }
}

/**
 * Time comes in following format: 08-11 20:03:17.182:
 * Parse into milliseconds
 * @param day string of format 08-11
 * @param hours string of format 20:03:17.182:
 * @return
 */
private long parseTime(String day, String hours)
{
    Time timeToSet = new Time();
    Time currentTime = new Time();
    currentTime.setToNow();

    // Parse fields
    String[] daySplits = day.split("-");
    if(daySplits.length < 2)
        return 0;

    String[] hourSplits = hours.split(":");
    if(hourSplits.length < 2)
        return 0;

    String[] secondSplits = hourSplits[2].split("\\.");
    if(secondSplits.length < 2)
        return 0;

    int _year = currentTime.year;
    int _month = Integer.parseInt(daySplits[0])-1;
    int _day = Integer.parseInt(daySplits[1]);
    int _hour = Integer.parseInt(hourSplits[0]);
    int _min = Integer.parseInt(hourSplits[1]);
    int _sec = Integer.parseInt(secondSplits[0]);
    int _mili = Integer.parseInt(secondSplits[1]);

    //set(int second, int minute, int hour, int monthDay, int month, int year)
    timeToSet.set(_sec, _min, _hour, _day, _month, _year);

    // return calculated value
    long parsedTimeInMili = timeToSet.toMillis(true) + (long)_mili;
    return parsedTimeInMili;
}

private String parseVerbosity(String s)
{
    return s.split("/")[0];
}

private String parseTag(String s)
{
    int verbosityLength = parseVerbosity(s).length() +1;
    String tagPart = s.substring(verbosityLength);
    return tagPart.split("\\(")[0];
}

private int parsePid(String s)
{
    try {
        String pidPart = s.split("\\(")[1];
        return Integer.parseInt(pidPart.split("\\)")[0]);
    } catch (Exception e) {
        e.toString();
    }
    return -1;
}
}
...