Як протестувати абстрактний клас на Java за допомогою JUnit?


87

Я новачок у тестуванні Java за допомогою JUnit. Я повинен працювати з Java, і я хотів би використовувати модульні тести.

Моя проблема полягає в тому, що я маю абстрактний клас з деякими абстрактними методами. Але є деякі методи, які не є абстрактними. Як я можу протестувати цей клас за допомогою JUnit? Приклад коду (дуже простий):

abstract class Car {

    public Car(int speed, int fuel) {
        this.speed = speed;
        this.fuel = fuel;
    }

    private int speed;
    private int fuel;

    abstract void drive();

    public int getSpeed() {
        return this.speed;
    }

    public int getFuel() {
        return this.fuel;
    }
}

Я хочу тестувати getSpeed()та getFuel()функціонувати.

Подібне запитання до цієї проблеми є і тут , але воно не використовує JUnit.

У розділі поширених запитань про JUnit я знайшов це посилання , але не розумію, що автор хоче сказати на цьому прикладі. Що означає цей рядок коду?

public abstract Source getSource() ;

4
Дивіться stackoverflow.com/questions/1087339/… щодо двох рішень за допомогою Mockito.
ddso,

Чи є якась перевага в вивченні іншої основи для тестування? Mockito - це лише розширення jUnit чи зовсім інший проект?
vasco

Mockito не замінює JUnit. Як і інші знущальні фреймворки, він використовується на додаток до модульного модульного тестування та допомагає створювати макетні об'єкти для використання у тестових випадках.
ddso,

Відповіді:


104

Якщо у вас немає конкретних реалізацій класу, а методи не є staticсенсом перевіряти їх? Якщо у вас є конкретний клас, ви будете тестувати ці методи як частину загальнодоступного API конкретного класу.

Я знаю, про що ви думаєте: "Я не хочу перевіряти ці методи знову і знову, саме тому я створив абстрактний клас", але мій аргумент протилежності полягає в тому, що сенс модульних тестів - дозволити розробникам вносити зміни, запустити тести та проаналізувати результати. Частина цих змін може включати перевизначення методів вашого абстрактного класу, як protectedі public, що може призвести до фундаментальних змін у поведінці. Залежно від характеру цих змін це може вплинути на те, як ваша програма працює несподіваними, можливо негативними способами. Якщо у вас хороший пакет модульних тестів, проблеми, що виникають внаслідок цих типів, повинні бути очевидними під час розробки.


17
100% охоплення коду - це міф. У вас повинно бути достатньо тестів, щоб охопити всі ваші відомі гіпотези про те, як повинна поводитися ваша програма (бажано писати перед написанням коду la Test Driven Development). На даний момент я працюю в дуже ефективно функціонуючій команді TDD, і на момент останньої збірки у нас є лише 63% охоплення, написаного в процесі розробки. Чи це добре? хто знає ?, але я вважав би марною тратою часу повертатися назад і намагатися підняти це вище.
nsfyn55

3
звичайно. Деякі стверджують, що це є порушенням належного TDD. Уявіть, що ви в команді. Ви робите припущення, що метод остаточний, і ви не ставите тести на будь-які конкретні реалізації. Хтось видаляє модифікатор і вносить зміни, які пульсують цілу гілку ієрархії успадкування. Чи не хочете ви, щоб ваш набір тестів це вловив?
nsfyn55

31
Я не погоджуюсь. Незалежно від того, працюєте ви в TDD чи ні, конкретний метод вашого абстрактного класу містить код, тому вони повинні мати тести (незалежно від того, є підкласи чи ні). Більше того, модульне тестування в тестах Java (зазвичай) класів. Тому насправді немає логіки в тестуванні методів, які не є частиною класу, а скоріше його супер класу. Дотримуючись цієї логіки, ми не повинні тестувати жоден клас на Java, за винятком класів, які взагалі не мають підкласів. Щодо методів, які перевизначені, саме тоді ви додаєте тест для перевірки змін / доповнень до тесту підкласу.
ethanfar

3
@ nsfyn55 Що, якби були конкретні методи final? Я не бачу причини для тестування того самого методу кілька разів, якщо реалізація не може змінитися
Діоксин

3
Чи не слід, щоб тести були націлені на абстрактний інтерфейс, щоб ми могли запускати їх для всіх реалізацій? Якщо це неможливо, ми порушуватимемо Ліскова, про що ми хотіли б знати та виправити. Тільки якщо реалізація додає якусь розширену (сумісну) функціональність, ми повинні мати спеціальний модульний тест для неї (і саме для цієї додаткової функціональності).
тн

36

Створіть конкретний клас, який успадковує абстрактний клас, а потім протестуйте функції, які конкретний клас успадковує від абстрактного класу.


Що ви зробите у випадку, якщо у вас є 10 конкретних класів, що розширюють абстрактний клас, і кожен із цих конкретних класів реалізує лише 1 метод, і скажімо, що інші 2 методи однакові для кожного з цих класів, оскільки вони реалізовані в рефераті клас? У моєму випадку я не хочу копіювати тести для абстрактного класу до кожного підкласу.
scarface

12

З прикладом класу, який ви опублікували, здається, немає сенсу тестувати, getFuel()і getSpeed()оскільки вони можуть повернути лише 0 (сетерів немає).

Однак, припускаючи, що це був лише спрощений приклад для ілюстративних цілей, і у вас є законні причини для тестування методів в абстрактному базовому класі (інші вже вказували на наслідки), ви можете налаштувати свій тестовий код так, щоб він створював анонімний підклас базового класу, який просто забезпечує фіктивні (не-оперативні) реалізації для абстрактних методів.

Наприклад, у вашому TestCaseви можете зробити це:

c = new Car() {
       void drive() { };
   };

Потім протестуйте інші методи, наприклад:

public class CarTest extends TestCase
{
    private Car c;

    public void setUp()
    {
        c = new Car() {
            void drive() { };
        };
    }

    public void testGetFuel() 
    {
        assertEquals(c.getFuel(), 0);
    }

    [...]
}

(Цей приклад базується на синтаксисі JUnit3. Для JUnit4 код буде дещо іншим, але ідея однакова.)


Дякую за відповідь. Так, мій приклад був спрощений (і не дуже хороший). Прочитавши всі відповіді тут, я написав фіктивний клас. Але, як писав @ nsfyn55 у своїй відповіді, я пишу тест для кожного нащадка цього абстрактного класу.
vasco

9

Якщо в будь-якому випадку вам потрібне рішення (наприклад, тому що у вас забагато реалізацій абстрактного класу, і тестування завжди повторювало б однакові процедури), ви можете створити абстрактний тестовий клас із абстрактним фабричним методом, який буде виконаний реалізацією цього тестовий клас. Ці приклади працюють або я з TestNG:

Абстрактний тестовий клас Car:

abstract class CarTest {

// the factory method
abstract Car createCar(int speed, int fuel);

// all test methods need to make use of the factory method to create the instance of a car
@Test
public void testGetSpeed() {
    Car car = createCar(33, 44);
    assertEquals(car.getSpeed(), 33);
    ...

Впровадження Car

class ElectricCar extends Car {

    private final int batteryCapacity;

    public ElectricCar(int speed, int fuel, int batteryCapacity) {
        super(speed, fuel);
        this.batteryCapacity = batteryCapacity;
    }

    ...

Одиниця тестовий клас ElectricCarTestз класу ElectricCar:

class ElectricCarTest extends CarTest {

    // implementation of the abstract factory method
    Car createCar(int speed, int fuel) {
        return new ElectricCar(speed, fuel, 0);
    }

    // here you cann add specific test methods
    ...

5

Ви могли б зробити щось подібне

public abstract MyAbstractClass {

    @Autowire
    private MyMock myMock;        

    protected String sayHello() {
            return myMock.getHello() + ", " + getName();
    }

    public abstract String getName();
}

// this is your JUnit test
public class MyAbstractClassTest extends MyAbstractClass {

    @Mock
    private MyMock myMock;

    @InjectMocks
    private MyAbstractClass thiz = this;

    private String myName = null;

    @Override
    public String getName() {
        return myName;
    }

    @Test
    public void testSayHello() {
        myName = "Johnny"
        when(myMock.getHello()).thenReturn("Hello");
        String result = sayHello();
        assertEquals("Hello, Johnny", result);
    }
}

4

Я б створив внутрішній клас jUnit, який успадковується від абстрактного класу. Це може бути інстанційовано і мати доступ до всіх методів, визначених у абстрактному класі.

public class AbstractClassTest {
   public void testMethod() {
   ...
   }
}


class ConcreteClass extends AbstractClass {

}

3
Це чудова порада. Однак його можна покращити, подавши приклад. Можливо, приклад класу, який ви описуєте.
SDJMcHattie

2

Ви можете створити екземпляр анонімного класу, а потім протестувати цей клас.

public class ClassUnderTest_Test {

    private ClassUnderTest classUnderTest;

    private MyDependencyService myDependencyService;

    @Before
    public void setUp() throws Exception {
        this.myDependencyService = new MyDependencyService();
        this.classUnderTest = getInstance();
    }

    private ClassUnderTest getInstance() {
        return new ClassUnderTest() {    
            private ClassUnderTest init(
                    MyDependencyService myDependencyService
            ) {
                this.myDependencyService = myDependencyService;
                return this;
            }

            @Override
            protected void myMethodToTest() {
                return super.myMethodToTest();
            }
        }.init(myDependencyService);
    }
}

Майте на увазі, що видимість має бути protectedвластивістю myDependencyServiceабстрактного класу ClassUnderTest.

Ви також можете акуратно поєднати цей підхід з Mockito. Дивіться тут .


2

Мій спосіб перевірити це досить просто в межах кожного abstractUnitTest.java. Я просто створюю клас у abstractUnitTest.java, який розширює абстрактний клас. І перевірити це таким чином.


0

Ви не можете перевірити цілий абстрактний клас. У цьому випадку у вас є абстрактні методи, це означає, що вони повинні бути реалізовані класом, який поширюється на даний абстрактний клас.

У цьому класі програміст повинен написати вихідний код, який присвячений його логіці.

Іншими словами, немає сенсу тестування абстрактного класу, оскільки ви не можете перевірити остаточну поведінку його.

Якщо у вас є основна функціональність, не пов’язана з абстрактними методами в якомусь абстрактному класі, просто створіть інший клас, де абстрактний метод викличе якийсь виняток.


0

Як варіант, ви можете створити абстрактний тестовий клас, що охоплює логіку всередині абстрактного класу, і розширити його для кожного тесту підкласу. Так що таким чином ви зможете забезпечити тестування цієї логіки для кожної дитини окремо.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.