Статьи

Управляемые данными тесты с JUnit 4 и Excel

Одна из приятных возможностей JUnit 4 — это параметризованные тесты, которые позволяют проводить тестирование на основе данных в JUnit с минимальными усилиями. Это достаточно просто и очень полезно для настройки базовых тестов, управляемых данными, определяя ваши тестовые данные непосредственно в вашем классе Java. Но что, если вы хотите получить свои тестовые данные откуда-то еще? В этой статье мы рассмотрим, как получить тестовые данные из электронной таблицы Excel.

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

В JUnit типичный параметризованный тест может выглядеть так:

@RunWith(Parameterized.class)
public class PremiumTweetsServiceTest {

private int numberOfTweets;
private double expectedFee;

@Parameters
public static Collection data() {
return Arrays.asList(new Object[][] { { 0, 0.00 }, { 50, 5.00 },
{ 99, 9.90 }, { 100, 10.00 }, { 101, 10.08 }, { 200, 18},
{ 499, 41.92 }, { 500, 42 }, { 501, 42.05 }, { 1000, 67 },
{ 10000, 517 }, });
}

public PremiumTweetsServiceTest(int numberOfTweets, double expectedFee) {
super();
this.numberOfTweets = numberOfTweets;
this.expectedFee = expectedFee;
}

@Test
public void shouldCalculateCorrectFee() {
PremiumTweetsService premiumTweetsService = new PremiumTweetsService();
double calculatedFees = premiumTweetsService.calculateFeesDue(numberOfTweets);
assertThat(calculatedFees, is(expectedFee));
}
}

Тестовый класс имеет переменные-члены, которые соответствуют входным значениям (numberOfTweets) и ожидаемым результатам (ОжидаемыйFee).
@RunWith (Parameterzed.class) аннотация получает JUnit вводить тестовые данные в экземпляры тестового класса с помощью конструктора.

Тестовые данные предоставляются методом с аннотацией @Parameters . Этот метод должен возвращать коллекцию массивов, но помимо этого вы можете реализовать его так, как хотите. В приведенном выше примере мы просто создаем встроенный массив в коде Java. Тем не менее, вы также можете получить его из других источников. Чтобы проиллюстрировать этот момент, я написал простой класс, который читает электронную таблицу Excel и предоставляет данные в ней в следующей форме:

@RunWith(Parameterized.class)
public class DataDrivenTestsWithSpreadsheetTest { 

    private double a;
    private double b;
    private double aTimesB;
   
    @Parameters
    public static Collection spreadsheetData() throws IOException {
        InputStream spreadsheet = new FileInputStream("src/test/resources/aTimesB.xls");
        return new SpreadsheetData(spreadsheet).getData();
    }

    public DataDrivenTestsWithSpreadsheetTest(double a, double b, double aTimesB) {
        super();
        this.a = a;
        this.b = b;
        this.aTimesB = aTimesB;
    }

    @Test
    public void shouldCalculateATimesB() {
        double calculatedValue = a * b;
        assertThat(calculatedValue, is(aTimesB));
    }
}

Электронная таблица Excel содержит таблицы умножения в трех столбцах:

Класс SpreadsheetData использует проект POI Apache для загрузки данных из электронной таблицы Excel и преобразования ее в список массивов объектов, совместимых с аннотацией @Parameters . Я поместил исходный код вместе с примерами модульного тестирования на BitBucket . Для любопытных класс SpreadsheetData показан здесь:

public class SpreadsheetData {

    private transient Collection data = null;

    public SpreadsheetData(final InputStream excelInputStream) throws IOException {
        this.data = loadFromSpreadsheet(excelInputStream);
    }

    public Collection getData() {
        return data;
    }

    private Collection loadFromSpreadsheet(final InputStream excelFile)
            throws IOException {
        HSSFWorkbook workbook = new HSSFWorkbook(excelFile);

        data = new ArrayList();
        Sheet sheet = workbook.getSheetAt(0);

        int numberOfColumns = countNonEmptyColumns(sheet);
        List rows = new ArrayList();
        List rowData = new ArrayList();

        for (Row row : sheet) {
            if (isEmpty(row)) {
                break;
            } else {
                rowData.clear();
                for (int column = 0; column < numberOfColumns; column++) {
                    Cell cell = row.getCell(column);
                    rowData.add(objectFrom(workbook, cell));
                }
                rows.add(rowData.toArray());
            }
        }
        return rows;
    }

    private boolean isEmpty(final Row row) {
        Cell firstCell = row.getCell(0);
        boolean rowIsEmpty = (firstCell == null)
                || (firstCell.getCellType() == Cell.CELL_TYPE_BLANK);
        return rowIsEmpty;
    }

    /**
     * Count the number of columns, using the number of non-empty cells in the
     * first row.
     */
    private int countNonEmptyColumns(final Sheet sheet) {
        Row firstRow = sheet.getRow(0);
        return firstEmptyCellPosition(firstRow);
    }

    private int firstEmptyCellPosition(final Row cells) {
        int columnCount = 0;
        for (Cell cell : cells) {
            if (cell.getCellType() == Cell.CELL_TYPE_BLANK) {
                break;
            }
            columnCount++;
        }
        return columnCount;
    }

    private Object objectFrom(final HSSFWorkbook workbook, final Cell cell) {
        Object cellValue = null;

        if (cell.getCellType() == Cell.CELL_TYPE_STRING) {
            cellValue = cell.getRichStringCellValue().getString();
        } else if (cell.getCellType() == Cell.CELL_TYPE_NUMERIC) {
            cellValue = getNumericCellValue(cell);
        } else if (cell.getCellType() == Cell.CELL_TYPE_BOOLEAN) {
            cellValue = cell.getBooleanCellValue();
        } else if (cell.getCellType()  ==Cell.CELL_TYPE_FORMULA) {
            cellValue = evaluateCellFormula(workbook, cell);
        }

        return cellValue;
    
    }

    private Object getNumericCellValue(final Cell cell) {
        Object cellValue;
        if (DateUtil.isCellDateFormatted(cell)) {
            cellValue = new Date(cell.getDateCellValue().getTime());
        } else {
            cellValue = cell.getNumericCellValue();
        }
        return cellValue;
    }

    private Object evaluateCellFormula(final HSSFWorkbook workbook, final Cell cell) {
        FormulaEvaluator evaluator = workbook.getCreationHelper()
                .createFormulaEvaluator();
        CellValue cellValue = evaluator.evaluate(cell);
        Object result = null;
        
        if (cellValue.getCellType() == Cell.CELL_TYPE_BOOLEAN) {
            result = cellValue.getBooleanValue();
        } else if (cellValue.getCellType() == Cell.CELL_TYPE_NUMERIC) {
            result = cellValue.getNumberValue();
        } else if (cellValue.getCellType() == Cell.CELL_TYPE_STRING) {
            result = cellValue.getStringValue();   
        }
        
        return result;
    }
}

Тестирование на основе данных — отличный способ более тщательно протестировать приложения, основанные на вычислениях. В реальных приложениях эта электронная таблица Excel может быть предоставлена ​​клиентом или конечным пользователем с бизнес-логикой, закодированной в электронной таблице. (Библиотека POI отлично справляется с числовыми вычислениями, хотя, похоже, у нее немного проблем с вычислениями по датам). В этом сценарии электронная таблица Excel становится частью ваших приемочных тестов и помогает определить ваши требования, позволяет эффективно разрабатывать сам код на основе тестов, а также действует как часть ваших приемочных тестов.

С http://weblogs.java.net/blog/johnsmart