Я слышал дискуссии о том, почему статическая типизация лучше тестирования (и наоборот) как способ уменьшить количество ошибок в системах, и я думаю, что такие дискуссии бесполезны, поскольку они сравнивают яблоки и апельсины.

Рассмотрим следующий фрагмент программы (на Python с подсказками типов).

def foo(i: int, j: int) -> bool:
    return i < j
def bar(i: int, j: int) -> bool:
    return i == j

Если рассматривать только сигнатуры типов этих функций, то имеем следующее.

foo: (int, int) -> bool
bar: (int, int) -> bool

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

foo("1", 3)   # first argument is not of int type
bar(3, [1,3]) # second argument is not of int type

Однако сигнатур типов недостаточно, чтобы пометить следующие вызовы как ошибочные.

if foo(3, 3):
    print("val1 is equal to val2")
if bar(3, 5):
    print("val1 is less than val2")

Теперь мы могли бы подумать, что эти ошибки не произошли бы, если бы функции были названы как equals и less_than. При переименовании обратите внимание, что мы не полагаемся на статическую типизацию, чтобы избежать ошибок :)

Тем не менее, давайте продолжим и переименуем функции.

def equals(i: int, j: int) -> bool:
    return i < j
def less_than(i: int, j: int) -> bool:
    return i == j
if equals(3, 3):
    print("val1 is equal to val2")
if less_than(3, 5):
    print("val1 is less than val2")

Даже сейчас фрагмент кода неисправен.

Проблема в том, что существует (по крайней мере) два типа типов, а мы указали только один тип. Для другого типа мы полагаемся на связь между именами функций и ожидаемым поведением функций.

Чтобы понять проблему, рассмотрим описание функции equals:

  1. Принимает два целых значения i и j и возвращает логическое значение.
  2. Возвращает true, если входные данные равны; ложно в противном случае.

Первая часть описания ограничивает значения, допустимые в качестве входных данных и выходных данных функции. Это описание является типом значения (домена) функции, которую мы указали с помощью подсказок типа (например, : int).

Вторая часть описания ограничивает поведение функции. Этот вид описания представляет собой поведенческий тип функции (такой, который может быть реализован по-разному). В приведенном выше примере мы не указывали такого рода информацию о типе.

Статическая типизация, доступная сегодня в большинстве языков программирования, предназначена только для указания типов значений (а не поведенческих типов).

Если вы все еще не уверены в поведенческих типах, рассмотрите возможность полагаться только на сигнатуру функции (List[int]) -> List[int] и выбрать функцию, которая сортирует заданный список целых чисел в порядке возрастания.

Как насчет тестирования?

В отличие от статической типизации (которая сегодня общедоступна), тестирование не касается типов значений.

Тестирование — это определение и проверка типов поведения.

В частности, тестовый пример фиксирует конкретное поведение функции (например, equals(3,3) должно оцениваться как истинное, equals(3,5) должно оцениваться как ложное), набор тестов фиксирует поведенческий тип (т. е. набор ожидаемых поведений) функции, а тестирование проверяет, соответствует ли реализация функции правильному типу поведения (т. е. функция демонстрирует ожидаемое поведение).

Чтобы провести параллели со статической типизацией, наборы тестов похожи на подсказки типов, а тестирование похоже на проверку типов.

Хотя тестирование может показаться лучше, чем статическая типизация, это не так. В отличие от статической типизации, тесты не являются ни краткими, ни исчерпывающими в определении возможного поведения тестируемой функции (в общем). Это верно даже для расширенных методов тестирования, таких как тестирование на основе свойств. Кроме того, использование тестирования как способа обеспечения правильности типа значения в целом недопустимо.

Итак, где это нас оставляет?

До тех пор, пока у нас не появятся системы статических типов, которые позволят нам легко и лаконично указывать как значения, так и поведенческие типы и использовать их для проверки правильности типов, нам понадобятся как статическая типизация, так и тестирование, чтобы уменьшить количество ошибок в системах.

Короче говоря, статическая типизация и тестирование дополняют друг друга. А сейчас нам нужно иметь возможность указывать и анализировать типы, а также создавать и выполнять тесты.