Подводные камни при написании юнит-тестов?

python
ruby
unittest
javascript
java
Теги: #<Tag:0x00007fedc74749a8> #<Tag:0x00007fedc7474868> #<Tag:0x00007fedc7474728> #<Tag:0x00007fedc74745e8> #<Tag:0x00007fedc74744a8>

(Sergei Chipiga) #1

Всем привет!

Подскажите пжста с юнит-тестированием.

При написании юнит-тестов частой проблемой является подмена функций, вызываемых внутри тестируемой функции (mocking/stubbing).

Очень интересует как это решается в таких языках программирования как Java и C/C++, где есть строгая типизация.

К примеру в python, ruby, js можно инжектить фейковые модули, классы, объекты, функции как аргументы функции или класса (dependency injection). Насколько я знаю, с Java понятие dependency injection также тесно связано. Интересно, как в java и плюсах подменяются импортированные классы и модули на фейковые при юнит-тестировании.


(Mark Dubrovskyi) #2

Dependency Injection как раз эту проблему и решает. Все зависимости приходят извне, соответственно в юнит тестах можно просто подсунуть моки в сеттеры или конструкторы.

С точки зрения строгой типизации, то тут тоже ничего военного. Мок должен быть того же типа, что и ожидаемый объект (наследоваться от того же класса и имплементить те же интерфейсы). С технической точки зрения реализуется либо dynamic proxy, либо генерацией класса-наследеника оригинального ожидаемого типа.

В джавке вообще есть библиотеки типа Mockito, которые очень упрощают написание моков/стаббов.


#3

В чем именно проблема подмены? Если речь идет о поиске конкретно инструмента, то для джавы это будет Mockito


(Sergei Chipiga) #4

@olyv Проблема может быть в том, что функция (класс, модуль) может быть недоступна снаружи (в тесте) для подмены из-за ограничений области видимости. Н-р в случае c javascript:

// file.js

var Server = require("some-module").Server;

module.exports.func = () => {
    var server = new Server({ host: "localhost", port: 8888 });
    server.start();
};

// test.js

var func = require("./file").func;

describe("my tests", () => {
    it("should start server", () => {
        func();  // here I can't observe server, because it's internal
    });
});

Но если поменять слегка код, то можно сделать инжекцию фейкового класса.

// file.js

var Server = require("some-module").Server;

module.exports.func = opts => {
    opts = opts || {};
    var S = opts.Server || Server;
    var server = new S({ host: "localhost", port: 8888 });
    server.start();
};

// test.js

var expect = require("chai").expect;
var sinon = require("sinon");

var func = require("./file").func;

describe("my tests", () => {
    var sOpts, start, Server;

    before(() => {
        Server = function (opts) {
            sOpts = opts;
            this.start = start = sinon.spy();
        };
    })

    it("should start server", () => {
        func({ Server: Server });
        expect(sOpts.host).to.be.equal("localhost");
        expect(sOpts.port).to.be.equal(8888);
        expect(start.calledOnce).to.be.true
    });
});

Или реальный пример из проекта гугла:

Интересуют подобные примеры, как инжекция классов или модулей делается в Java и C++.


#5

Я затрудняюсь перевести это на джаву поскольку слабоват в js.


(Sergei Chipiga) #6

@olyv,

А не найдется пара ссылок на хорошие примеры юниттестов на java c mockito?


#7

@Sergei_Chipiga мне вот этот пример кажется очень удачным https://dzone.com/articles/a-guide-to-mocking-with-mockito


(Sergei Chipiga) #8

благодарю :slightly_smiling_face: