Тестирование кода

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

Инструкции по созданию тестов доступны в руководствах по созданию RObject-конфигураций элементов экспертизы.

Заглушки

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

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

Заглушки не должны создавать ложных срабатываний или неправильных выводов в процессе выполнения теста.

Заглушки используются в следующих случаях:

  • Изоляция сложных зависимостей: заглушки могут заменить сложные или зависимые компоненты, которые сложно или невозможно протестировать, как, например, внешние веб-сервисы.

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

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

Пример 1. Пример теста с заглушкой
...
tests:
  - name: Privilege escalation from admin to root
    events:
       - { "uid": "root", "suid": "admin", "cmd": "pytohn", "sproc":"python", "dproduct": "Ubuntu", "dvchost": "ubnt.land", "dvendor": "Linux", "mode": "0100600", "msg": [ "audit(1694421703.884:27001658):" ], "name": "", "params": [ "-m" ], "sourceIp": "10.150.50.115", "suser": "1000", "syscall": "221", "duid": "0"}
    assertion: !vrl |
      true

В данном примере заглушка используется для тестирования случая повышения привилегий с пользователя admin до root. В контексте данного теста не происходит выполнения реальных операций с привилегиями или вызовов системы, а проверка сводится к простому утверждению true.

Неполное написание тестов

Тесты должны быть достаточно полными, чтобы гарантировать проверку всех ключевых параметров и состояний, особенно при работе с более сложными функциями или обработкой данных.

Ниже приведен пример неполного теста, который проверяет только одно условие (параметр appname):

Пример 2. Пример неполного теста
...
tests:
  - name: "Test"
    events:
      - { "raw": {"appname": "testapp", "facility": "kern", "message": "test message"} }
    assertion: !vrl |
      assert_eq!(.[0].appname, "testapp")

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

Оптимизированный тест должен включать проверки для всех важных параметров, таких как facility и message:

Пример 3. Пример оптимизированного теста
...
tests:
  - name: "Test"
    events:
      - { "raw": {"appname": "testapp", "facility": "kern", "message": "test message"} }
    assertion: !vrl |
      assert_eq!(.[0].appname, "testapp")
      assert_eq!(.[0].facility, "kern")
      assert_eq!(.[0].message, "test message")

Рассмотрим пример теста нормализации, где необходимо проверить несколько полей.

Пример 4. Пример неполного теста для проверки нормализации
...
normalizer: !vrl |
  .dproduct = "metrics"
  .dvendor = to_string(.raw.namespace) ?? ""
  .name = to_string(.raw.tags.component_id) ?? ""
  .cnt = to_string(.raw.counter.value) ?? ""
  .cat = to_string(.raw.name) ?? ""
tests:
  - name: add_resource1
    events:
     - { "raw":  {"appname":"testtag","facility":"kern","hostname":"host001","message":"13.03.2017 17:54.25 '# UserGate Server# ' 192.168.30.71 -> 188.40.238.250:80 0/440","procid":14884,"severity":"info","timestamp":"2024-02-27T16:14:41Z"}}
    assertion: !vrl |
      assert_eq!(.[0].bytesOut, null)

Для того чтобы исправить тест, нужно добавить проверку для каждого поля:

Пример 5. Пример оптимизированного теста
...
tests:
  - name: "testing"
    events:
      - raw: {"counter":{"value":1751.0},"kind":"absolute","name":"component_received_event_bytes_total","namespace":"vector","tags":{"component_id":"unique-id-12345","component_kind":"transform","component_type":"remap","host":"collector-host-01"},"timestamp":"2024-05-29T10:26:42.688089484Z"}
    assertion: !vrl |
      assert_eq!(.[0].cat, "component_received_event_bytes_total")
      assert_eq!(.[0].cnt, "1751")
      assert_eq!(.[0].dvendor, "vector")
      assert_eq!(.[0].name, "unique-id-12345")
      assert_eq!(.[0].dproduct, "metrics")

Проверка всех сценариев

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

Тестирование всех сценариев выполнения правил включает проверку корректной обработки событий, обработки некорректных данных, обработки исключений и проверки работы на граничных условиях.

Тесты можно разделить на несколько категорий:

  • Позитивные сценарии: проверка, что правило корректно обрабатывает события, соответствующие условиям.

  • Негативные сценарии: проверка, что некорректные или неподходящие события не приводят к срабатыванию правила.

  • Граничные случаи: тестирование минимальных, максимальных значений и редких комбинаций.

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

Для достижения полной проверки сценариев следует:

  • Убедиться, что тесты охватывают все логические ветки правил.

  • Проверить реакции правил на отсутствующие, лишние или неверные данные.

  • Включить тесты, моделирующие исключительные ситуации, например, пустые события, неожиданные форматы данных.

Пример 6. Пример теста
tests:
  - name: "Scenario 1: Basic network connection"
    description: Проверка корректного определения сетевого подключения.
    events:
      - { "type": "network", "action": "connect", "source_ip": "192.0.2.1" }
    assertion: !vrl |
      assert_eq!(.[0].type, "network") &&
      assert_eq!(.[0].action, "connect") &&
      assert_eq!(.[0].source_ip, "192.0.2.1")

  - name: "Eventid 7: wmic process list using wmiscript.xsl"
    description: Проверка обработки события с использованием `wmic` и `wmiscript.xsl`.
    events:
      - { "dvchost": "host1.domain.local", "fname": "C:\\Windows\\System32\\wmiscript.dll" }
    assertion: !vrl |
      assert_eq!(.[0].dvchost, "host1.domain.local") &&
      assert_eq!(.[0].fname, "C:\\Windows\\System32\\wmiscript.dll")

  - name: "Eventid 7: msxsl processing msxscript.xsl"
    description: Проверка корректного распознавания использования `msxsl` и `msxscript.xsl`.
    events:
      - { "dvchost": "host1.domain.local", "fname": "C:\\Windows\\System32\\msxscript.dll" }
    assertion: !vrl |
      assert_eq!(.[0].dvchost, "host1.domain.local") &&
      assert_eq!(.[0].fname, "C:\\Windows\\System32\\msxscript.dll")

  - name: "Eventid 4688: msxsl loading xsl over network"
    description: Проверка загрузки `.xsl` через SMB-сетевой путь.
    events:
      - { "dvchost": "host1.domain.local", "fname": "\\x.x.x.x\\mysmbshare\\file1.xsl" }
    assertion: !vrl |
      assert_eq!(.[0].dvchost, "host1.domain.local") &&
      assert_eq!(.[0].fname, "\\x.x.x.x\\mysmbshare\\file1.xsl")

  - name: "Eventid 1: wmic process execution"
    description: Проверка выполнения команды через `wmic`.
    events:
      - { "dvchost": "host1.domain.local", "fname": "C:\\Windows\\System32\\cmd.exe" }
    assertion: !vrl |
      assert_eq!(.[0].dvchost, "host1.domain.local") &&
      assert_eq!(.[0].fname, "C:\\Windows\\System32\\cmd.exe")

  - name: "Eventid 1: msxsl executing calc.exe"
    description: Проверка выполнения `calc.exe` через `msxsl`.
    events:
      - { "dvchost": "host1.domain.local", "sproc": "C:\\Windows\\System32\\cmd.exe" }
    assertion: !vrl |
      assert_eq!(.[0].dvchost, "host1.domain.local") &&
      assert_eq!(.[0].sproc, "C:\\Windows\\System32\\cmd.exe")

  - name: "Eventid 1: msxsl.exe from known hash"
    description: Проверка выполнения `msxsl.exe` из известного пути.
    events:
      - { "dvchost": "host1.domain.local", "dproc": "C:\\System\\Payloads\\msxsl.exe" }
    assertion: !vrl |
      assert_eq!(.[0].dvchost, "host1.domain.local") &&
      assert_eq!(.[0].dproc, "C:\\System\\Payloads\\msxsl.exe")

  - name: "Eventid 1: msxsl.exe from random file"
    description: Проверка выполнения произвольного файла вместо ожидаемого.
    events:
      - { "dvchost": "host1.domain.local", "dproc": "C:\\System\\Payloads\\456.exe" }
    assertion: !vrl |
      assert_eq!(.[0].dvchost, "host1.domain.local") &&
      assert_eq!(.[0].dproc, "C:\\System\\Payloads\\456.exe")

Тесты на производительность

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

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

Пример 7. Пример теста на производительность
...
benches:
  - name: "Performance Test"
    repetitions: 10000
    events:
      - { "raw": "2022-08-11T12:32:41.000Z 5.86.210.12 - user=user2 act=file op=del host=pc1 name=pomodoro.txt" }
...

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