предположим, что у нас есть такой метод:
func method(intr MyInterface) {
go intr.exec()
}
В модульном тестировании method
мы хотим утверждать, что inter.exec
вызывался один раз и только один раз; так что мы можем смоделировать его с помощью другой фиктивной структуры в тестах, которая даст нам функциональность, чтобы проверить, был ли он вызван или нет:
type mockInterface struct{
CallCount int
}
func (m *mockInterface) exec() {
m.CallCount += 1
}
А в модульных тестах:
func TestMethod(t *testing.T) {
var mock mockInterface{}
method(mock)
if mock.CallCount != 1 {
t.Errorf("Expected exec to be called only once but it ran %d times", mock.CallCount)
}
}
Теперь проблема в том, что, поскольку intr.exec
вызывается с ключевым словом go
, мы не можем быть уверены, что когда мы достигаем нашего утверждения в тестах, оно вызывается или нет.
Возможное решение 1:
Добавление канала к аргументам intr.exec
может решить эту проблему: мы можем ждать получения любого объекта из него в тестах, а после получения объекта от него мы можем продолжать утверждать, что он вызывается. Этот канал будет полностью не использован в производственных (не тестовых) кодах.
Это будет работать, но это добавляет ненужную сложность к не тестовым кодам и может сделать большие кодовые базы непонятными.
Возможное решение 2:
Добавление относительно небольшого сна в тесты до утверждения может дать нам некоторую уверенность в том, что вызов программы будет вызван до того, как сон закончится:
func TestMethod(t *testing.T) {
var mock mockInterface{}
method(mock)
time.sleep(100 * time.Millisecond)
if mock.CallCount != 1 {
t.Errorf("Expected exec to be called only once but it ran %d times", mock.CallCount)
}
}
Это позволит оставить не тестовые коды такими, какие они есть сейчас.
Проблема в том, что это сделает тесты медленнее и сделает их нестабильными, поскольку они могут сломаться при некоторых случайных обстоятельствах.
Возможное решение 3:
Создание служебной функции следующим образом:
var Go = func(function func()) {
go function()
}
и переписать method
вот так:
func method(intr MyInterface) {
Go(intr.exec())
}
В тестах мы могли бы изменить Go
на это:
var Go = func(function func()) {
function()
}
Итак, когда мы запускаем тесты, intr.exec
будет вызываться синхронно, и мы можем быть уверены, что наш ложный метод вызывается до подтверждения.
Единственная проблема этого решения состоит в том, что оно переопределяет фундаментальную структуру golang, что неправильно.
Это решения, которые я мог бы найти, но, насколько я вижу, они не являются удовлетворительными. Какое решение лучше?