Личные грабли при написании тестов разработчиком
          Алексей Золотых (@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;
          
        Плюсы
Минусы
I get paid for code that works, not for tests, so my philosophy is to test as little as possible to reach a given level of confidence...
А что говорит наука?
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 |