Я имею честь работать с людьми, которые будут проводить тестирование впервые.

То есть… это возможность понаблюдать за людьми, которые годами программируют, но ни разу не тестировали, то ли из-за «нехватки времени», то ли из-за того, что не считают это важным.

Как бы то ни было… Я буду держать вас в курсе.

Что вы тестируете?

Это, наверное, первое, что я заметил, просматривая первые тесты чего-то, что «уже работает».

Кодекс - это закон!

Если вы тестируете что-то, что «уже работает», то этот код — закон.

И вы должны проверять то, что он говорит, а не то, что вы думаете или что вы хотите, чтобы он делал. Это может показаться противоречивым, но если вы хотите, чтобы ваш код что-то делал, у вас должны быть строки кода, которые обеспечивают то, что вы хотите, иначе вы получите что-то, что может работать только из-за удачи.

Пример

Мы начали тестировать «UserCase», который является посредником между «контроллером» и репозиторием/базой данных.

class RepositoryExample {
	async getAll(someFilter){...}
}
class UserCaseExample {
	constructor(repository){
		this.repository = repository;
	}
	
	validate(someData){...}
	async run(someData){
		if(!validate(someData)){
			// just an example, don't throw plain objects around
			throw {message: '!!!', code: 42};
		}
		return this.repository.getAll(someData);
	}
}
class ControllerExample {
	async getArrayOfSomething(someData){
		const repository = new RepositoryExample();
		const userCase = new UserCaseExample(repository);
		
		try {
			return userCase.run(someData);
		} catch ({message, code}) {
			HTMLException(message, code);
		}
	}
}

Я старался сделать это как можно проще, но что бы вы протестировали в UserCaseExample?

Первая попытка

// UserCaseExample.test.js
class MockingRepository {
	constructor(){
		this.database = [...]
	}
	
	async getAll(someFilter){
		return this.database.filter(...)
	}
}
describe('Testing UserCaseExample', () => {
  it('Valid return of repository data' () => {
    const uc = new UserCaseExample(new MockingRepository());
    const resultArr = uc.run('test');
	
    expect(resultArr.length > 0 && Array.isArray(resultArr)).toBe(true)
    for (result of resultArr){
      expect(result).toBe(
        expect.objectContaining({
          key1: expect.stringContaining('test'),
          key2: expect.any(Number),
          key3: expect.any(String),
        })
      )
    }
  })
})

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

Еще одна вещь, не входящая в сферу охвата, — это инструмент, который вы используете. Мы используем Jest, и недостаток знаний о моках, сопоставлениях и использовании Jest API был очевиден. Но знание инструмента — это то, чему вам так или иначе придется научиться, и каждый инструмент будет отличаться. Опять же, не стесняйтесь, как бы вы реорганизовали приведенный выше тест, если мы проигнорируем основную проблему.

Главная проблема

Цель ясна. Они ожидали массив, и тест проверяет, что они получают массив.

Но, видите ли… кодекс — это закон, и ни в коем случае UserCaseExample не упоминайте массив. Как есть, он просто берет результат из репозитория и передает его дальше.

Это означает, что тест должен только проверять, что все, что вы передаете в репозиторий, является тем, что вы получаете обратно (это может быть array, null или string, все равно). Вы также можете проверить, передаются ли данные фильтра (и вы можете сделать это, используя правильные макеты).

Если важно, чтобы вы получили array, то решение также очевидно… измените код, чтобы отразить, что вы хотите получить array.

Я вижу, что это будет номер один, о котором мне придется постоянно напоминать людям при тестировании:

«Что вы тестируете?»

И я буду продолжать говорить, что код — это закон, поэтому пусть код «говорит» вам, что вы должны тестировать. И если вы тестируете что-то, чего нет в коде… то вам следует сначала изменить код!

Фото на обложке Mufid Majnun на Unsplash