forked from testdouble/test-smells
-
Notifications
You must be signed in to change notification settings - Fork 0
/
bury-the-lede.js
130 lines (117 loc) · 5.62 KB
/
bury-the-lede.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
/* Тянуть резину
*
* Запах: подготовка теста настолько утомительна, что когда читатель добирается
* до проверок, он уже не помнит о чем тест
*
* Причины:
* 1. Функция работает со сложными моделями, требующими тяжелых валидаций
* или сохранения в БД.
*
* Лечение: если возможно, разделите сохранение, валидацию и логику,
* работающую с этими моделями. (Это проще сказать,
* чем сделать во фреймворках, предпочитающих смешивать эти
* функции.) После разделения, пишите раздельные тесты для моделей
* и логики, работающей с ними.
*
* Если так сделать не получается, вытащите логику в отдельный
* модуль, работающий с объектами похожими на модели. Возможно,
* вы потеряете покрытие у валидаций, но, скорее всего, оно
* останется в других местах.
*
* 2. Интеграционный тест проверяет средний слой приложения
* (HTTP контроллер, объект-репозиторий). Тест слишком сфокусированный,
* чтобы использовать фикстуры, а упрощенных данных недостаточно.
*
* Лечение: интеграционные тесты, не проверяющие приложение так, как это
* сделал бы пользователь, обречены столкнуться с этой проблемой.
* Наш совет: взвесьте пользу от таких тестов и либо
* не используйте их, либо используйте по минимуму.
* Если они вам все-таки нужны, соберите общий набор
* аккуратных фикстур, упрощающий подготовку таких тестов.
*
* Замечания к примеру:
* Это один из самых сложных примеров. У тестируемого модуля две
* ответственности: запросить адрес из данных кредитки по ее идентификатору
* через связь с пользователем, а затем убедиться, что почтовый индекс
* совпадает с тем, что ввели. Проблема в том, что у всех трех моделей
* сложные валидации, которые нельзя упростить без того,
* чтобы не застабить их.
*
* Попробуйте уменьшить боль, вытащив настройку моделей (и спрятав
* реальную причину проблем), либо застабив `repo.find` для всех объектов
* (и получив «фантастический тест»), либо разбив тестируемый модуль на два:
* одна функция запрашивает объекты, другая сверяет данные.
*/
// Тестируемый модуль
function verifyCardholderZip (cardId, zip) {
var card = repo.find(cardId)
var user = repo.find(card.userId)
var address = repo.find(user.addressId)
return address.zip === zip
}
// Тесты
module.exports = {
trueIfZipMatches: function () {
var address = {street: '123 Sesame', city: 'Cbus', state: 'OH', zip: '42'}
repo.save(address)
var user = {
addressId: address.id,
name: 'Jane',
age: 12,
income: '$12.48'
}
repo.save(user)
var issuer = {bankName: 'Bank Co'}
repo.save(issuer)
var card = {
userId: user.id,
apr: 17.8,
number: '1234 0000 2828 4494',
ccv: 364,
issuerId: issuer.id
}
repo.save(card)
var result = verifyCardholderZip(card.id, '42')
assert.equal(result, true)
}
}
// Фейковая реализация
var repo = {
__items: {},
nextId: 1,
save: function (obj) {
if (!obj.id) obj.id = repo.nextId++
// Gotcha!
if (obj.zip) validateAddress(obj)
if (obj.addressId) validateUser(obj)
if (obj.userId) validateCard(obj)
repo.__items[obj.id] = obj
},
find: function (id) {
return repo.__items[id]
}
}
function validateAddress (address) {
requireProperties(address, ['street', 'city', 'state'])
}
function validateUser (user) {
requireProperties(user, ['name', 'age', 'income'])
}
function validateCard (card) {
requireProperties(card, ['apr', 'number', 'ccv'])
requireRelation(card, 'issuerId', 'bankName')
}
function requireProperties (obj, props) {
props.forEach(function (prop) {
if (!obj.hasOwnProperty(prop)) {
throw new Error('ERROR: "' + prop + '" required on ' + JSON.stringify(obj))
}
})
}
function requireRelation (obj, idKey, prop) {
var relation = repo.find(obj[idKey])
if (!relation || !relation.hasOwnProperty(prop)) {
throw new Error('ERROR: "' + prop + '" required on "' + idKey + '" of ' +
JSON.stringify(obj))
}
}