Статьи

Как использовать JUnit для тестирования приложений JavaFX с IDE NetBeans


Итак, у нас есть
JavaFX 1.2 и
обновленная поддержка среды IDE NetBeans 6.5.1 . После нескольких часов использования я могу подтвердить, что опыт программиста теперь действительно улучшился, с редактором, гораздо более поддерживающим, чем раньше. Чего еще не хватает важности? Простой ответ: поддержка JUnit.

На самом деле, как ни странно, тесты не поддерживаются; даже не определенная папка для их размещения. Несколько человек писали о некоторых хитростях по запуску JUnit в проекте JavaFX, рассматривая JUnit как простую библиотеку; но этот подход меня не удовлетворил, в том числе потому, что я хочу что-то, что создает регулярные отчеты (например, для использования Хадсоном) и не требует написания слишком большого дополнительного кода (например, явное создание TestSuites); Одним словом, то, что позволит мне работать так же, как и тогда, когда тесты JUnit будут доступны в IDE NetBeans.

Я смог это сделать ценой применения двух небольших патчей к JUnit. Вот как это сделать в шесть шагов.

1. Создайте новый проект JavaFX и установите зависимость от вашего основного проекта.

Вы должны сделать это, потому что вы не хотите помещать тестовые источники вместе с обычными источниками. Вы должны создать класс Dummy.fx с функцией void run () и объявить его в качестве основного класса в свойствах проекта: NetBeans рассматривает каждый проект как законченное приложение и требует точки входа.

public function run(): Void
{
}

Вам нужно поместить JAR JUnit в качестве библиотеки вашего тестового проекта — но исправленную версию JUnit (см. Ниже).

2. Создать тесты

Держите обычные соглашения, такие как помещение теста в ту же упаковку, что и у прибора; Назовите тест с помощью **** Тестовой таблицы и тестовых методов испытаний *** (). Поскольку в JavaFX нет аннотаций, вы должны использовать подход JUnit 3.x, то есть расширять TestCase. Я включаю образец реального теста от BlueBill Mobile (который для краткости опущено лишь несколько не относящихся к делу вещей). Обратите внимание, что для утверждения равенства двух последовательностей мне пришлось написать небольшую специальную функцию, поскольку последовательности не являются массивами и не известны JUnit. Такой код (и другие подобные), вероятно, должен быть упакован в определенную небольшую библиотеку.

package it.tidalwave.bluebillmfx.taxon.controller;

import java.lang.System;
import it.tidalwave.bluebillmfx.taxon.model.Taxon;
import it.tidalwave.bluebillmfx.taxon.model.TaxonomyMock;
import it.tidalwave.bluebillmfx.taxon.model.TaxonomyImpl;
import junit.framework.TestCase;
import org.junit.Assert;

public class TaxonSearchControllerTest extends TestCase
{
def mockTaxonomy = TaxonomyMock{};
def fullTaxonomy = TaxonomyImpl{};

postinit
{
def is = getClass().getResourceAsStream("EBNItalia2003.json");
fullTaxonomy.load(is);
is.close();
}

def fixture = InstrumentedTaxonSearchController
{
taxons: mockTaxonomy.species;
}

def performanceFixture = InstrumentedTaxonSearchController
{
taxons: fullTaxonomy.species;
}

public function testFunction(): Void
{
assertFilter("", ["Airone cinerino", "Airone bianco maggiore", "Airone rosso", "Piro piro", "Piro piro boschereccio"], -1, "");
assertFilter("A", ["Airone cinerino", "Airone bianco maggiore", "Airone rosso"], -1, "Airone ");
...
}

function assertFilter (filter : String, expected : String[], expectedIndex : Integer, expectedLeading : String)
{
fixture.filter = filter;
assertEquals(expected, displayNames(fixture.filteredTaxons));
assertEquals("fixture.selectedTaxonIndex", expectedIndex, fixture.selectedTaxonIndex);
assertEquals("fixture.leading", expectedLeading, fixture.leading);
}

function assertEquals (expected : String[], actual : String[]) : Void
{
Assert.assertTrue("{actual}", expected == actual);
}
}

3. Создайте конкретную цель Ant для тестирования

Он в значительной степени скопирован из аналогичного материала в NetBeans, просто исправлен для получения нужного материала в пути к классам (это можно сделать лучше, но в настоящее время это работает).

    <target name="test" depends="init,compile">
<mkdir dir="${build.test.unit.results.dir}"/>
<junit showoutput="true" fork="true" failureproperty="tests.failed" errorproperty="tests.failed" filtertrace="${test.filter.trace}" tempdir="${build.test.unit.results.dir}">
<batchtest todir="${build.test.unit.results.dir}">
<fileset dir="${build.test.unit.classes.dir}">
<include name="**/*Test.class"/>
</fileset>
</batchtest>
<classpath>
<fileset dir="${platform.fxhome}/lib/shared">
<include name="*.jar"/>
</fileset>
<fileset dir="${platform.fxhome}/lib/desktop">
<include name="*.jar"/>
</fileset>
<pathelement path="${javac.classpath}"/>
<pathelement path="${build.classes.dir}"/>
<pathelement path="${build.test.unit.classes.dir}"/>
</classpath>
<!-- syspropertyset refid="test.unit.properties"/ -->
<jvmarg value="-ea"/>
<formatter type="brief" usefile="false"/>
<formatter type="xml"/>
</junit>
<fail if="tests.failed" unless="continue.after.failing.tests">Some tests failed; see details above.</fail>
</target>

4. Добавьте несколько свойств в nbproject / project.properties

build.test.unit.classes.dir=${build.dir}/compiled
build.test.unit.results.dir=${build.dir}/test/results/

5. Патч Юнит

Теперь самая раздражающая часть. Все бы уже работало, но для деталей: JUnit будет жаловаться на тестовые классы, имеющие более одного конструктора. К сожалению, хотя JavaFX не имеет конструкторов в языке, он создает синтетический код с двумя конструкторами в байт-коде (один без параметров и один с логическим параметром). Я не знаю, для чего это, но наверняка это заставляет JUnit жаловаться. Мы должны исправить JUnit. Я скачал JUnit 4.6 (держу пари, что все в порядке, также 4.5) и исправил эти два файла:

src/main/java/org/junit/runners/model/TestClass.java
src/main/java/org/junit/runners/BlockJUnit4ClassRunner.java
diff TestClass.java TestClass.java.orig 
31,33c31,33
< // if (klass != null && klass.getConstructors().length > 1)
< // throw new IllegalArgumentException(
< // "Test class can only have one constructor");
---
> if (klass != null && klass.getConstructors().length > 1)
> throw new IllegalArgumentException(
> "Test class can only have one constructor");


diff BlockJUnit4ClassRunner.java BlockJUnit4ClassRunner.java.orig
118,121c118,121
< // if (!hasOneConstructor()) {
< // String gripe= "Test class should have exactly one public constructor";
< // errors.add(new Exception(gripe));
< // }
---
> if (!hasOneConstructor()) {
> String gripe= "Test class should have exactly one public constructor";
> errors.add(new Exception(gripe));
> }

Это в основном устраняет применение одного конструктора.

Я считаю, что JUnit довольно расширяемый, и можно указать его собственного бегуна. Возможно, можно написать некоторый код и встроить его вместе с тестами, а не исправлять оригинал, но, к сожалению, для настройки этого материала требуются аннотации, а в JavaFX нет аннотаций …

 

6. Создайте небольшой скрипт Ant для оболочки (только для Hudson)

Хорошо, в настоящее время у нас есть два проекта NetBeans, обычный и один с тестами. Если вы хотите запустить этот материал под Hudson, поместите их оба в каталог и создайте этот небольшой скрипт:

<?xml version="1.0" encoding="UTF-8"?>
<project name="blueBillMFX-global" default="hudson-fast" basedir=".">

<target name="hudson-fast">
<ant dir="blueBill-mobileFX" target="clean" inheritall="false"/>
<ant dir="blueBill-mobileFX" target="default" inheritall="false"/>
<ant dir="blueBill-mobileFXTest" target="clean" inheritall="false"/>
<ant dir="blueBill-mobileFXTest" target="test" inheritall="false"/>
</target>

</project>

По сути, первый вызов ant может быть излишним, так как сборка тестового проекта приведет к компиляции первого проекта.

 

Хорошо, это, безусловно, можно улучшить, но пока что это просто работает, и вы можете начать писать тесты, как и должно быть. Единственное важное ограничение — это то, что он не будет работать для проекта, настроенного с помощью профиля Mobile, из-за того, что звучит как ошибка компилятора JavaFX . Мой проект для мобильного профиля, но я удостоверяюсь, что Хадсон настраивает его как профиль рабочего стола. Я надеюсь исправить это в ближайшее время. Может быть, тем временем у нас будет финальная версия NetBeans 6.7 с поддержкой JavaFX + JUnit? Кто знает.

О чем ты говоришь? О, вы хотели бы увидеть пример проекта, чтобы попробовать. Хорошо, blueBill Mobile не готов к прайм-тайму, но если вы просто хотите посмотреть, как я настроил тесты, вы можете проверить это в Kenai:

svn co -r 141 https://kenai.com/svn/bluebill-mobile~svn/trunk/src/FX