Артем Десятников
Самый интуитивный метод
Иногда нельзя иначе
Первого рода — посчитать плохую программу хорошей
Второго рода — посчитать хорошую программу плохой
Тестирование минимальных кусочков приложения по-отдельности.
Надо сделать их независимыми
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("Первая тудушка"));
}
[TestFixture]
public class TodoListTest
{
[Test]
public void HasNothingToDoWhenEmpty()
{
var list = new TodoList(new TodoItem[] {});
Assert.That(list.HasSmthToDo, Is.False);
}
}
describe("todo list", () => {
it("has nothing to do when empty", () => {
let list = new TodoList([]);
expect(list.hasSmthToDo).to.be.false;
});
});
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);
}
[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));
Затычки на каком-то уровне. Поверх - настоящая логика
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);
}
Чистые функции. Декомпозиция.
Тестирование в браузере