Личные грабли при написании тестов разработчиком
Алексей Золотых (@zolotyh)
Нет никакой полной и вменяемой классификации
Какой тест модульный?
it("1 плюс 2 равно 3", () => {
expect(sum(1, 2)).toBe(3);
});
А этот модульный?
it("Поле чудес! Передаем приветы!", () => {
act(() => {
render(<Hello name="Margaret" />, container);
});
expect(container.textContent).toBe("Hello, Margaret!");
});
А этот?
test("Поле чудес! Теперь async", async () => {
render(<Fetch url="/greeting" />)
fireEvent.click(screen.getByText('Load Greeting'))
await waitFor(() => screen.getByRole('heading'))
expect(screen.getByRole('heading')).toHaveTextContent('hello there')
expect(screen.getByRole('button')).toBeDisabled()
})
Шкала
Надежность и качество
function isValidEmail(email) {
const re = /^.../;
return re.test(String(email).toLowerCase());
}
test("Should return true on hello@example.com", () =>
expect(isValidEmail("hello@example.com"))
.toEqual(true);
)
[
'hello@hello.com',
'superpuper@hello.com',
'blblblbl@hello.ru',
'русскоеимя@изроссии.рф',
].forEach((item) => {
test("Should return true on ${item}", () =>
expect(isValidEmail(item))
.toEqual(true);
)
});
Фильтрация списка
Фильтрация списка
const defaultItems = [
'react',
'angular',
'vuejs',
'svelte'
];
export const Frameworks = (items = defaultItems) => {
const [value, setValue] = useState('');
const filteredItems = items
.filter((item) => item.includes(value))
.map((item) => <li key={item}>{item}</li>);
return (
<form>
<input type="text" onChange={(e) => setValue(e.target.value)} />
{filteredItems}
</form>
);
};
Как выглядит тест
test('should work', () => {
const wrapper = shallow(<FilterForm />);
expect(wrapper.find('li').length).to.equal(4);
wrapper.find('input')
.simulate('change', {
target: {value: 'react'}
});
expect(wrapper.find('li').length).to.equal(1);
});
Что тестируем на самом деле
.filter(e => e.includes(this.state.value))
Как можно переписать
const filter = (list, value) => {
return list.filter(e => e.includes(value))
}
expect(filter(list, "react").length)
.to.equal(1);
Выделяем обособленные модули
const filter = (list, value) => {
return list.filter(e => {
if(isBlackListed(e)){
return false;
}
return e.includes(value)
})
}
jest.mock('./isBackListed');
it("...", () => {
isBlackListed.mockReturnedValue(false);
expect(filter(list, "react").length)
.to.equal(0);
})
Не тащим в аргументы ненужное
const foo = (settings) => {
const hasPermissions = settings
.profile
.permissions
.indexOf('readItems') !== -1;
...
}
const mockSettings = {
profile: {
permissions: ['readItems']
}
}
Не тащим в аргументы ненужное
const foo = ({isAbleToRead}) => {/*...*/}
Покрытие
А что говорит наука?
А что говорит здравый смысл?
Нужно ли покрывать?
const setUpAxios = ({ baseURL }) => {
const instanse = axios.create({ baseURL,})
axios.interceptors.response.use(on401error);
return instanse;
};
jest.mock('axios');
...
axiosInterceptorMock = jest.fn();
const mock = {
interceptors: {
response: {
push: axiosInterceptorMock,
}
}
}
axios.createInstance.mockReturnedValue(mock);
expect(axiosInterceptorMock).toHaveBeenCalled();
Вредно тестировать имплементацию!
Если контракта нет, есть смысл тестировать более высокий уровень
test('loads and displays greeting', async () => {
render(<FetchGreeting />)
userEvent.click(screen.getByText('Load Greeting'))
await screen.findByRole('heading')
expect(screen.getByRole('heading')).toHaveTextContent('hello there')
expect(screen.getByRole('button')).toHaveAttribute('disabled')
})
Требования к виджету будут изменяться реже чем требования к коду
Snapshots
it('renders correctly', () => {
const tree = renderer
.create(
<Link page="http://www.facebook.com">
Facebook
</Link>
)
.toJSON();
expect(tree).toMatchSnapshot();
});
TDD
TDD
Писат нужно минимально необходимый код для того, чтобы прошел тест
const sum = () => ({});
it("", () => {
expect(sum(1,2)).toEqual(3);
})
const sum = () => 3;
Плюсы
Минусы
А что говорит наука?
bit.ly/3E5IzGtThis indicates that TDD is an effective paradigm when developing small repositories but may not be particularly effective when developing larger code repositories.
Еще одно исследование
статья - bit.ly/3lYN6Co
первоисточник - bit.ly/3ufcGHe
Drawing general conclusions from empirical studies in software engineering is difficult because any process depends to a large degree on a potentially large number of relevant context variables. For this reason, we cannot assume a priori that the results of a study generalize beyond the specific environment in which it was conducted
Типы могут заменить некоторые тесты
const Foo = ({user}) => <div>{user.userrname}</div>
PropertyBased тестирование
youtu.be/H-cBhNMxlCwНизкоуровневые тесты. Выводы
Как делаю я
Сначала были написаны тест кейсы, потом автоматизация
Результаты
Все тестировалось сразу, тесты были частью задач
Помните?
test("Should return true on hello@example.com", () =>
expect(isValidEmail("hello@example.com"))
.toEqual(true);
)
А давайте из него сделаем E2E
Достоинства
Недостатки
BDD !== TDD
Я
Выводы
О соотношении тестов в проекте
О соотношении тестов в проекте
Спасибо!
twitter.com/zolotyh | |
telegram | t.me/zolotyh |