Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add unit test and integration test #34

Open
wants to merge 19 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
root = true
[*]
charset = utf-8
end_of_line = lf
indent_size = 2
indent_style = space
insert_final_newline = true
max_line_length = 80
trim_trailing_whitespace = true
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
node_modules
node_modules
coverage
26 changes: 26 additions & 0 deletions .hermione.conf.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
const path = require('path');

module.exports = {
baseUrl: 'http://localhost:3000',
gridUrl: 'http://0.0.0.0:4444/wd/hub',
compositeImage: true,

browsers: {
chrome: {
desiredCapabilities: {
browserName: 'chrome'
}
},
firefox: {
desiredCapabilities: {
browserName: 'firefox'
}
}
},
plugins: {
'html-reporter/hermione': {
path: 'hermione/hermione-html-report'
},
[path.resolve(__dirname, './hermione/custom-commands/index.js')]: true
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

удобнее было настроить в package.json обращение к собственному плагину как к пакету npm
https://medium.com/@the1mills/how-to-test-your-npm-module-without-publishing-it-every-5-minutes-1c4cb4b369be

}
};
60 changes: 60 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,63 @@ npm start
- нужно добавить в README список логических блоков системы и их сценариев
- для каждого блока нужно написать модульные тесты
- если необходимо, выполните рефакторинг, чтобы реорганизовать логические блоки или добавить точки расширения

## Логические блоки и их сценарии

- Создание хлебных крошек
- В хлебных крошках присутствует элемент, указывающий на главную страницу.
- На странице второго уровня в хлебных крошках присутствует второй элемент.
- На страницах выше второго уровня, в хлебные крошки добавляются элементы в соответствии с уровнем страницы.
- Последний элемент хлебных крошек никуда не ссылается.

- Создание ссылок
- Для папок создаются правильные ссылки.
- Для файлов создаются правильные ссылки.

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

- История коммитов
- Количество возвращаемых элементов соответствует переданным параметрам.
- Правильно разбирается история коммитов.

- Файловая система коммита
- Возвращается список файлов для определенного коммита.
- При выборе папки из коммита, возвращается список файлов этой папки.
- Правильно разбирается файловая система коммита.

- Содержимое файла из коммита
- Возвращается содержимое файла.

В сценарии содержимого файла из коммита отсутствует логика, а так же он покрыт интеграционным тестом, поэтому для него нет необходимости писать модульный тест.

- Контроллер истории коммитов
- Подготовка истории коммитов перед отправкой в представление.
- Используется представление истории коммитов.
- В представление прокидывается набор данных, состоящий из заголовка, хлебных крошек и списка истории коммитов.

- Контроллер файловой системы коммита
- Подготовка файловой системы коммита перед отправкой в представление.
- Используется представление файловой системы коммита.
- В представление прокидывается набор данных, состоящий из заголовка, хлебных крошек и файловой системы коммита.

- Контроллер содержимого файла коммита
- Используется представление содержимого файла коммита.
- В представление прокидывается набор данных, состоящий из заголовка, хлебных крошек и содержимого файла коммита коммита.

Для всех контроллеров потребовался рефакторинг, чтобы появилась возможность протестировать данные, передаваемые в их представления.

В контроллере содержимого файла нет необходимости проверки файла на тип, потому что данный контроллер вызывается только для файлов у которых тип равен blob.

## Запуск модульного тестирования

```
npm run unit-test
```

## Запуск интеграционного тестирования

```
selenium-standalone start
npm run dev
npm run integration-test
```
38 changes: 38 additions & 0 deletions controllers/__test__/contentController.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
const { expect } = require('chai');
const sinon = require('sinon');
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

круто, что получилось разобраться с библиотекой sinon.js


const controller = require('../contentController');

describe('Контроллер содержимого файла коммита', () => {

const request = { params: { hash: '', '0': '' } };
Copy link
Owner

@dima117 dima117 Nov 3, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

чтобы создавать заглушки для запроса и ответа express, удобно использовать готовые библиотеки вроде https://www.npmjs.com/package/sinon-express-mock

const response = { render: sinon.spy() };
const stubs = {};
stubs.gitFileTree = sinon.stub();
stubs.gitFileTree.returns(Promise.resolve([
{ type: '', hash: '', path: '' }
]));
stubs.gitFileContent = sinon.stub();
stubs.buildBreadcrumbs = sinon.stub();
Copy link
Owner

@dima117 dima117 Nov 3, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

эти переменные - общие для всех тестов на контроллер
тесты не должны использовать общие экземпляры объектов


it('Используется представление содержимого файла коммита', async () => {
await controller(request, response, () => {}, stubs);

const view = response.render.getCall(0).args[0];

expect(view).to.be.equal('content');
});

it('В представление прокидывается набор данных, состоящий из заголовка, хлебных крошек и содержимого файла коммита коммита', async () => {
await controller(request, response, () => {}, stubs);

const params = response.render.getCall(1).args[1];
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

если запустить этот тест отдельно от первого, то он упадет


expect(params).to.have.all.keys(
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

не проверяется контент полученного объекта

'title',
'breadcrumbs',
'content',
);
});

});
46 changes: 46 additions & 0 deletions controllers/__test__/filesController.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
const { expect } = require('chai');
const sinon = require('sinon');

const controller = require('../filesController');

describe('Контроллер файловой системы коммита', () => {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

здесь те же ошибки, что и в предыдущем файле


const request = { params: { hash: '' } };
const response = { render: sinon.spy() };
const stubs = {};
stubs.gitFileTree = sinon.stub();
stubs.gitFileTree.returns(Promise.resolve([
{ type: '', hash: '', path: '' }
]));
stubs.buildObjectUrl = sinon.stub();
stubs.buildBreadcrumbs = sinon.stub();

it('Подготовка файловой системы коммита перед отправкой в представление', async () => {
await controller(request, response, () => {}, stubs);

const files = response.render.getCall(0).args[1].files;

expect(files[0]).to.have.any.keys('href', 'name');
});

it('Используется представление содержимого файла коммита', async () => {
await controller(request, response, () => {}, stubs);

const view = response.render.getCall(1).args[0];

expect(view).to.be.equal('files');
});

it('В представление прокидывается набор данных, состоящий из заголовка, хлебных крошек и файловой системы коммита', async () => {
await controller(request, response, () => {}, stubs);

const params = response.render.getCall(2).args[1];

expect(params).to.have.all.keys(
'title',
'breadcrumbs',
'files',
);
});

});
45 changes: 45 additions & 0 deletions controllers/__test__/indexController.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
const { expect } = require('chai');
const sinon = require('sinon');

const controller = require('../indexController');

describe('Контроллер истории коммитов', () => {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

здесь те же ошибки, что и в предыдущем файле


const response = { render: sinon.spy() };
const stubs = {};
stubs.gitHistory = sinon.stub();
stubs.gitHistory.returns(Promise.resolve([
{ hash: '', author: '', timestamp: '', msg: '' }
]));
stubs.buildFolderUrl = sinon.stub();
stubs.buildBreadcrumbs = sinon.stub();

it('Подготовка истории коммитов перед отправкой в представление', async () => {
await controller(null, response, () => {}, stubs);

const list = response.render.getCall(0).args[1].list;

expect(list[0]).to.have.any.keys('href');
});

it('Используется представление истории коммитов', async () => {
await controller(null, response, () => {}, stubs);

const view = response.render.getCall(1).args[0];

expect(view).to.be.equal('index');
});

it('В представление прокидывается набор данных, состоящий из заголовка, хлебных крошек и списка истории коммитов', async () => {
await controller(null, response, () => {}, stubs);

const params = response.render.getCall(2).args[1];

expect(params).to.have.all.keys(
'title',
'breadcrumbs',
'list',
);
});

});
42 changes: 19 additions & 23 deletions controllers/contentController.js
Original file line number Diff line number Diff line change
@@ -1,28 +1,24 @@
const { gitFileContent, gitFileTree } = require('../utils/git');
const { buildFolderUrl, buildBreadcrumbs } = require('../utils/navigation');
const { buildBreadcrumbs } = require('../utils/navigation');

module.exports = async function(req, res, next, ...stubs) {
const _stubs = (stubs && stubs[0]) || {};
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

некрасивый костыль - заглушки передаются как массив, хотя это один объект
лучше было переписать на класс или фабрику

const _gitFileTree = _stubs.gitFileTree || gitFileTree;
const _gitFileContent = _stubs.gitFileContent || gitFileContent;
const _buildBreadcrumbs = _stubs.buildBreadcrumbs || buildBreadcrumbs;

module.exports = function(req, res, next) {
const { hash } = req.params;
const path = req.params[0].split('/').filter(Boolean);
const path = req.params[0].split('/').filter(Boolean).join('/');

const content = await _gitFileTree(hash, path).then(
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

здесь смешиваются промисы и async/await
лучше придерживаться одного синтаксиса

Copy link
Owner

@dima117 dima117 Nov 3, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

еще, кажется, если при получении контента произойдет 500 ошибка, то все равно вызовется render (из 19 строки)

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

кстати, на ошибку при получении контента из git тоже можно было написать тест

([file]) => _gitFileContent(file.hash),
/* istanbul ignore next */
err => next(err)
);

gitFileTree(hash, path.join('/'))
.then(function([file]) {
if (file && file.type === 'blob') {
return gitFileContent(file.hash);
}
})
.then(
content => {
if (content) {
res.render('content', {
title: 'content',
breadcrumbs: buildBreadcrumbs(hash, path.join('/')),
content
});
} else {
next();
Copy link
Owner

@dima117 dima117 Nov 3, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

кажется, во время рефакторинга сломалась 404 ошибка, если запросить несуществующий путь

}
},
err => next(err)
);
res.render('content', {
title: 'content',
breadcrumbs: _buildBreadcrumbs(hash, path),
content
});
};
27 changes: 17 additions & 10 deletions controllers/filesController.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const {
buildBreadcrumbs
} = require('../utils/navigation');

/* istanbul ignore next */
function buildObjectUrl(parentHash, { path, type }) {
switch (type) {
case 'tree':
Expand All @@ -16,26 +17,32 @@ function buildObjectUrl(parentHash, { path, type }) {
}
}

module.exports = function(req, res, next) {
module.exports = async function(req, res, next, ...stubs) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

те же ошибки, что и в других контроллерах

const _stubs = (stubs && stubs[0]) || {};
const _gitFileTree = _stubs.gitFileTree || gitFileTree;
const _buildObjectUrl = _stubs.buildObjectUrl || buildObjectUrl;
const _buildBreadcrumbs = _stubs.buildBreadcrumbs || buildBreadcrumbs;

const { hash } = req.params;
const pathParam = (req.params[0] || '').split('/').filter(Boolean);

const path = pathParam.length ? pathParam.join('/') + '/' : '';

return gitFileTree(hash, path).then(
const files = await _gitFileTree(hash, path).then(
list => {
const files = list.map(item => ({
return list.map(item => ({
...item,
href: buildObjectUrl(hash, item),
href: _buildObjectUrl(hash, item),
name: item.path.split('/').pop()
}));

res.render('files', {
title: 'files',
breadcrumbs: buildBreadcrumbs(hash, pathParam.join('/')),
files
});
},
/* istanbul ignore next */
err => next(err)
);

res.render('files', {
title: 'files',
breadcrumbs: _buildBreadcrumbs(hash, pathParam.join('/')),
files
});
};
26 changes: 16 additions & 10 deletions controllers/indexController.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,26 @@
const { gitHistory } = require('../utils/git');
const { buildFolderUrl, buildBreadcrumbs } = require('../utils/navigation');

module.exports = function(req, res) {
gitHistory(1, 20).then(
module.exports = async function(req, res, next, ...stubs) {
const _stubs = (stubs && stubs[0]) || {};
const _gitHistory = _stubs.gitHistory || gitHistory;
const _buildFolderUrl = _stubs.buildFolderUrl || buildFolderUrl;
const _buildBreadcrumbs = _stubs.buildBreadcrumbs || buildBreadcrumbs;

const list = await _gitHistory(1, 20).then(
history => {
const list = history.map(item => ({
return history.map(item => ({
...item,
href: buildFolderUrl(item.hash, '')
href: _buildFolderUrl(item.hash, '')
}));

res.render('index', {
title: 'history',
breadcrumbs: buildBreadcrumbs(),
list
});
},
/* istanbul ignore next */
err => next(err)
);

res.render('index', {
title: 'history',
breadcrumbs: _buildBreadcrumbs(),
list
});
};
1 change: 1 addition & 0 deletions hermione/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
hermione-html-report
18 changes: 18 additions & 0 deletions hermione/custom-commands/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
const assert = require('assert');

module.exports = (hermione, opts) => {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

круто, что получилось написать плагин для гермионы

hermione.on(hermione.events.NEW_BROWSER, browser => {
browser.addCommand('assertNavigation', (selector, expectSelector) => {
return browser
.isExisting(selector)
.then(exists => {
assert.ok(exists, 'Ссылка для перехода не найдена');
})
.click(selector)
.isExisting(expectSelector)
.then(exists => {
assert.ok(exists, 'Переход по ссылке происходит некорректно');
});
});
});
};
Loading