Про Тестирование – Автоматические тесты – Плохой пример номер раз



Про Тестирование – Автоматические тесты – Плохой пример номер раз

0 0


myf-testing


On Github a10nik / myf-testing

Про Тестирование

Артем Десятников

Программа работает хорошо?

  • посмотреть на код
  • протестировать ее работу

Протестировать вручную

Самый интуитивный метод

Иногда нельзя иначе

иначе тест Тьюринга

AI

Обработка картинок

Автоматические тесты

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

Автоматические тесты

Минусы

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

Ошибки классификации

Первого рода — посчитать плохую программу хорошей

  • неожиданный сценарий
  • неожиданное рабочее окружение (браузер, сервисы)

Второго рода — посчитать хорошую программу плохой

  • слишком сложные тесты
  • устаревшие тесты
  • неожиданное тестовое окружение
<1>- смена организации в кевебе. половина багов в ие8.сервисы, перетирающие реквизиты. площадки разных команд. <2>- таймауты. смена верстки. брэндинг. настройки на соседних сервисах.

Конфликт

простота vs реалистичность

Простота. Юнит-тесты

Тестирование минимальных кусочков приложения по-отдельности.

Надо сделать их независимыми

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

Пример TODO-MVC

Плохой пример номер раз

class TodoList {
  private TodoItem[] items;
  public TodoList() {
    this.items = Db.GetTodoItems();
  }
}
void MainLogic() {
  var list = new TodoList();
  Render(list);
}

Плохой пример номер раз

class TodoList {
  private TodoItem[] items;
  public TodoList() {
    this.items = Db.GetTodoItems();
  }
}
void Test() {
  var list = new TodoList();
  ???
}

Dependency Injection

Исправленный пример номер раз

class TodoList {
  private TodoItem[] items;
  public TodoList(TodoItem[] items) {
    this.items = items;
  }
}
void MainLogic() {
  var db = new Db("login", "pass", catalog: "items");
  var items = db.GetTodoItems();
  var list = new TodoList(items);
  Render(list);
}
void Test() {
  var list = new TodoList(new[] { new TodoItem() { Text = "Первая тудушка", Done = false } });
  var rendered = Render(list);
  Assert.That(rendered, Contains.Substring("Первая тудушка"));
}

NUnit

[TestFixture]
public class TodoListTest
{
  [Test]
  public void HasNothingToDoWhenEmpty()
  {
    var list = new TodoList(new TodoItem[] {});
    Assert.That(list.HasSmthToDo, Is.False);
  }
}

Mocha + Chai

describe("todo list", () => {
  it("has nothing to do when empty", () => {
    let list = new TodoList([]);
    expect(list.hasSmthToDo).to.be.false;
  });
});

Семантичные Assert-методы

NUnit

Assert.AreNotEqual(5, 2 + 2);
// vs
Assert.That(2 + 2, Is.Not.EqualTo(5));
Assert.That(SomeMethod, Throws.TypeOf()
    .With.Property("Parameter").EqualTo("myParam"));

Плохой номер два

class Db {
  ...
  public TodoItem[] GetTodoItems() {
    return new DbConnection(login, password, catalog)
      .Query("select * from todos")
      .Select(row => new TodoItem
      {
        Text = row["text"],
        Done = row["done"] == "1"
      });
  }
  public void SaveTodoItems(TodoItem[] items) {
    new DbConnection(login, password, catalog)
      .Query("insert or update items " + ...));
  }
}

Dependency Inversion

Исправленный пример номер два

class Db {
  private IDbConnection connection;
  ...
  public TodoItem[] GetTodoItems() {
    return connection
      .Query("select * from todos")
      .Select(row => new TodoItem
      {
        Text = row["text"],
        Done = row["done"] == "1"
      });
  }
  public void SaveTodoItems(TodoItem[] items) {
    connection.Query("insert or update items " + ...);
  }
}

Dependency Inversion

Исправленный пример номер два. Тест

class TestEmptyConnection : IDbConnection {
  public RowSet Query(string query) { return new RowSet(); }
  public void Dispose() {}
}
[Test]
void AllDoneWhenListIsEmpty() {
  var db = new Db(new TestEmptyConnection());
  Assert.That(db.GetTodoItems(), Is.Empty);
}

Исправленный пример номер два. FakeItEasy

[Test]
void DbYieldsEmptyItemListWhenRowSetIsEmpty() {
  var fakeConnection = A.Fake();
  A.CallTo(() => fakeConnection.Query("select * from todos"))
    .Returns(new RowSet());
  var db = new Db(fakeItemProvider);
  Assert.That(db.GetTodoItems(), Is.Empty);
}

Интеграционный тест

new MainLogic(new Db(fakeConnection));

Затычки на каком-то уровне. Поверх - настоящая логика

Single Responsibility

class DbUtils {
  public TodoItem RowToTodoItem(Row row) { ... }
  public string ToUpdateQuery(TodoItem[] items) { ... }
}
[Test]
void RowToTodoItemConvertsSimpleRow() {
  var row = new Row();
  row.Add("done", "0");
  row.Add("text", "hello");
  var todoItem = DbUtils.RowToTodoItem(row);
  Assert.That(todoItem.Text, Is.EqualTo("hello"));
  Assert.That(todoItem.Done, Is.False);
}

Чистые функции. Декомпозиция.

End-To-End тесты

Тестирование в браузере

хрупкие. и ошибки расплывчатые
гугл. разница в понимании. сначала - e2e и unit. потом - integration. искусство

THE END

Про Тестирование Артем Десятников