Skip to main content

Testing Failure Modes: Fault Injection for Safety-Related SystemsTestowanie rodzajów uszkodzeń: wstrzykiwanie uszkodzeń w systemach bezpieczeństwa

How to test what happens when things break: FMEA-driven test cases, fault injection at software and hardware level, and how chaos engineering and property-based testing relate to EN 50128 and ISO 26262.Jak sprawdzić, co się dzieje, gdy coś się psuje: przypadki testowe wyprowadzone z FMEA, wstrzykiwanie uszkodzeń w oprogramowaniu i sprzęcie oraz miejsce inżynierii chaosu i testów opartych na właściwościach wobec EN 50128 i ISO 26262.

Most test suites concentrate on the happy path: valid inputs, working hardware and messages that arrive on time. In a safety-related system that is the least interesting part.

The safety argument rests on what the system does when a sensor sticks, a message is corrupted or a task overruns. Those situations do not occur on their own during a normal test campaign.

This article is for engineers who verify safety-related systems, and for software engineers who know fault injection from distributed systems and want to see how it maps to functional safety.

I refer to EN 50128 and EN 50129 for railway and to ISO 26262 for road vehicles. The design side of the same topic is covered in safety in system design.

Why the happy path is not enough

Safety mechanisms such as watchdogs, plausibility checks, cross-comparison and message timeouts do nothing while everything works. They are dormant code and dormant hardware, and dormant parts are exactly where latent faults hide.

There are three reasons to test them deliberately:

  1. Evidence for the safety case. The safety case claims that certain faults are detected and lead to the safe state. Verification has to show that this happens in the real implementation, not only on paper.
  2. Diagnostic coverage. Hardware metrics in IEC 61508, EN 50129 and ISO 26262 depend on the share of dangerous failures that diagnostics detect. Fault injection is one of the ways to support the coverage values you claim.
  3. Timing. A detection that arrives too late is a failed safety mechanism. Only a test with a real fault shows the actual detection and reaction time.

Important

The pass criterion of a failure mode test is not “the system kept working”. It is “the fault was detected, and the system reached the defined safe state within the required time”.

Which failure modes to test

The table below groups typical failure modes of an embedded safety-related system and the mechanisms that are expected to catch them.

CategoryExample failure modesTypical safety mechanism
Sensor and inputStuck-at value, out of range, drift, noise, open or short circuitRange and plausibility checks, comparison of redundant sensors
CommunicationCorruption, repetition, deletion, insertion, resequencing, delay, masqueradeSafety code, sequence numbers, timeouts, source identifiers (EN 50159)
Timing and executionTask overrun, stuck program, wrong program flowWatchdog, program flow monitoring, deadline monitoring
Memory and processorBit flips in RAM, corrupted ROM, faulty ALUMemory tests, CRC over code and data, CPU self-tests, ECC
Power and outputSupply dropout or brown-out, output stuck onVoltage monitoring, output read-back, de-energise to trip
Data and configurationWrong or inconsistent configuration dataData validation, checksums, independent data preparation checks

The communication row follows the threat list of EN 50159, which is the usual reference for safety-related communication in railway systems.

Distributed-systems engineers will recognise most of the other rows. They are the same failure modes as network timeouts, duplicated messages and resource exhaustion, only with a stricter required reaction.

From FMEA to test cases

A failure mode and effects analysis (FMEA) already contains most of what a fault injection test needs. Each row names a failure mode, its effect, how it is detected and how the system reacts. If the analysis claims a detection, the test campaign should show it.

  +--------------+    +------------------+    +------------------+
  | FMEA row     |--->| Safety mechanism |--->| Fault injection  |
  | failure mode |    | requirement      |    | test case        |
  | + effect     |    | (detect, react,  |    | (inject, observe |
  +--------------+    |  time limit)     |    |  safe state)     |
         ^            +------------------+    +--------+---------+
         |                                             |
         +-------------- test result ------------------+
                 (confirms or corrects the FMEA)

The diagram shows the loop. The FMEA leads to requirements for the safety mechanisms, those requirements lead to test cases, and the test results flow back.

If a test shows that a fault is not detected, or is detected too late, the FMEA was wrong. The safety argument has to be corrected, not only the code.

The table shows how one FMEA row becomes a test case. The values are illustrative.

FMEA fieldContentTest case element
Failure modeSpeed sensor A stuck at last valueFault to inject: freeze sensor A output
Local effectMeasured speed no longer follows the real speedStimulus: change real speed (simulated)
DetectionCross-check of sensor A against sensor BExpected: discrepancy fault raised
ReactionSafe state: brake commandExpected: brake command issued
Time requirementDetection and reaction within the FTTIMeasured: time from injection to brake command

In my work with FMEA and FTA on safety-related railway systems, the most useful habit has been to keep this link explicit in the requirements tool: failure mode → safety requirement → test case → result.

A gap anywhere in that chain is a gap in the safety argument, and reviewers will find it.

Fault injection techniques

Faults can be injected at different levels. The closer the level is to the real system, the more realistic the test, but the harder it is to control and to repeat.

LevelHow faults are injectedGood for
Model or simulationFaults switched on in a model of the system or plantEarly checks of the safety concept and reaction times
Software unitStubs and mocks return errors, invalid values or late answersDefensive code, error paths, input validation
Software integrationCorrupted variables or messages via test hooks or a debuggerInterfaces between components, end-to-end error handling
Hardware-in-the-loopSignal simulators, bus error injection, pin-level faultsDiagnostics, timing, interaction of hardware and software
SystemDisconnected cables, power interruptions, corrupted telegramsValidation of the safety case on the real configuration

Software-implemented fault injection

At unit and integration level the simplest technique is to replace a dependency with a stub that misbehaves: it returns an error code, an out-of-range value or nothing at all. The test then checks that the component under test reacts as specified.

This is the same idea as mocking a failing HTTP client in a web service. The difference is the expected result: a defined safe reaction, not just “no crash”.

Test hooks that corrupt data at runtime are powerful, but they are extra code in the product. Keep them out of the released build or prove that they cannot be activated in operation.

Hardware and system-level injection

At hardware-in-the-loop and system level, faults are injected into real signals: a sensor input forced to a fixed value, a communication bus with corrupted frames, a supply voltage dip.

These tests show the real detection and reaction times, including the hardware and the operating system. A unit test cannot do that.

What the standards say

ISO 26262 lists fault injection testing among the methods for verifying hardware, software and the integrated system, with stronger recommendations at higher ASILs.

EN 50128 does not use the term chaos engineering, but its catalogue of techniques includes related ones, again selected according to the software integrity level:

  • boundary value analysis,
  • error guessing,
  • error seeding,
  • avalanche or stress testing.

For the full technique tables, see CENELEC EN 50128 and the notes on its successor in EN 50128 vs EN 50716.

Note

Always check the technique tables in the edition of the standard that applies to your project. The names and recommendation levels differ between standards and editions.

Chaos engineering through a safety lens

Chaos engineering grew out of large internet services. Teams deliberately kill instances, add latency or cut network links in production to check that the service stays available.

The method is built around a hypothesis:

  1. Define the normal “steady state”.
  2. Predict that it will hold when a certain fault occurs.
  3. Inject the fault and compare.

That hypothesis-driven structure is valuable for safety work too. The difference is in the hypothesis and in the environment.

AspectChaos engineering (IT services)Safety-related fault injection
HypothesisService stays available under the faultSystem detects the fault and reaches the safe state in time
EnvironmentOften production, with a limited blast radiusLab, test bench, HIL rig or test track, never revenue service
Fault selectionRandom or based on experienceSystematic, derived from FMEA, FTA and the hazard log
Success measureError rate and latency stay within limitsDetection, safe state and timing as specified
EvidenceDashboards and post-mortemsTest specifications, reports and traceability for the safety case

Never inject faults into a system in safety-related operation

Injecting faults into a train, a vehicle or an interlocking in service relies on the very safety mechanisms you are trying to test. Do it in a controlled test environment with independent protection in place.

The useful lesson from chaos engineering is the attitude: assume that failure modes exist which nobody wrote into the FMEA, and look for them on purpose.

Random fault campaigns on a test bench can complement the systematic, FMEA-driven tests. Sometimes they reveal combinations that the analysis missed.

Property-based testing of safety properties

Example-based tests check single scenarios. Property-based testing states a property that must hold for all inputs and lets a tool generate many random inputs and event sequences to look for a counterexample.

Safety requirements are often already written as such properties:

  • “The output is never more permissive than the inputs allow.”
  • “An invalid message never changes the value used by the safety function.”
  • “If no valid message has arrived for longer than the timeout, the value is the safe value.”

The sketch below tests the last two properties on a Python model of the safe message receiver from safety in system design. It uses the Hypothesis library. It is a model-level example, not a test of the real C code.

from hypothesis import given, strategies as st

SAFE_SPEED = 0       # most restrictive value
TIMEOUT_MS = 500


class Receiver:
    def __init__(self):
        self.last_seq = 0
        self.last_valid_ms = 0
        self.speed = SAFE_SPEED

    def on_msg(self, seq, speed, code_ok, now):
        if code_ok and seq > self.last_seq:
            self.last_seq = seq
            self.last_valid_ms = now
            self.speed = speed

    def cycle(self, now):
        if now - self.last_valid_ms > TIMEOUT_MS:
            self.speed = SAFE_SPEED


# One event: time step, sequence number, speed, safety code ok, delivered
events = st.lists(st.tuples(
    st.integers(0, 2000), st.integers(0, 100), st.integers(0, 300),
    st.booleans(), st.booleans()))


@given(events)
def test_receiver_safety_properties(evts):
    r, now = Receiver(), 0
    for dt, seq, speed, code_ok, delivered in evts:
        now += dt
        before = r.speed
        if delivered:
            r.on_msg(seq, speed, code_ok, now)
        if not (delivered and code_ok):
            # property 1: an invalid or lost message never changes the value
            assert r.speed == before
        r.cycle(now)
        if r.speed != SAFE_SPEED:
            # property 2: a permissive value is never older than the timeout
            assert now - r.last_valid_ms <= TIMEOUT_MS

Lost, corrupted, repeated and delayed messages are all generated by the same strategy, so one test explores thousands of fault sequences.

When a property fails, the tool shrinks the input to a minimal counterexample. That is often the clearest description of a design flaw you will get.

Tip

Property-based tests complement the requirements-based tests and coverage measurements that the standards require. They do not replace them. A good use is to explore the state space of a model or a unit early, before the formal test campaign.

Testing the tests

A fault injection campaign is only as good as its ability to fail. Two techniques help to check that:

  • Error seeding or mutation testing: introduce known faults into the code and check that the test suite detects them. Surviving mutants point to weak tests.
  • Negative checks of the safety mechanism: temporarily disable a diagnostic in a test build and confirm that the corresponding test fails. If it still passes, it was not testing the diagnostic.

A practical starting point

You do not need a full HIL rig to start:

  1. Take the FMEA (or the hazard log) and list every failure mode for which a detection is claimed.
  2. Write one test per claimed detection at the lowest level where the fault can be injected realistically.
  3. Assert detection, safe state and time, not only “no crash”.
  4. Trace each test to its failure mode and safety requirement.
  5. Add random fault sequences or property-based tests where the state space is too large for hand-written cases.

Common mistakes

  1. Testing only that the function works. The safety mechanisms are the part that needs fault injection, and they are silent on the happy path.
  2. Asserting “no exception” instead of the safe state. Surviving a fault is not the requirement. Reaching the defined safe state is.
  3. Ignoring timing. A test without a time measurement cannot show that the reaction fits within the FTTI.
  4. Choosing faults at random only. Random campaigns are a supplement. The safety case needs systematic coverage of the analysed failure modes.
  5. Leaving fault injection hooks active in the product. Test code that can corrupt data must not be reachable in operation.
  6. Not feeding results back. A failed fault injection test means the analysis was wrong too. Update the FMEA, not only the code.

Further reading

Większość zestawów testów skupia się na ścieżce pozytywnej (happy path): poprawne dane wejściowe, sprawny sprzęt, komunikaty przychodzące na czas. W systemie związanym z bezpieczeństwem to akurat najmniej ciekawa część.

Argumentacja bezpieczeństwa opiera się na tym, co system zrobi, gdy czujnik się zawiesi, komunikat zostanie przekłamany albo zadanie nie zmieści się w swoim czasie. Podczas zwykłej kampanii testowej takie sytuacje same się nie zdarzą.

Artykuł jest dla inżynierów, którzy weryfikują systemy związane z bezpieczeństwem, a także dla programistów, którzy znają wstrzykiwanie uszkodzeń z systemów rozproszonych i chcą zobaczyć, jak przekłada się ono na bezpieczeństwo funkcjonalne.

Dla kolei odwołuję się do EN 50128 i EN 50129, dla pojazdów drogowych do ISO 26262. Projektową stronę tego samego tematu opisałem w artykule bezpieczeństwo w projektowaniu systemów.

Dlaczego sama ścieżka pozytywna nie wystarczy

Mechanizmy bezpieczeństwa – watchdogi, kontrole wiarygodności, porównanie krzyżowe, limity czasu komunikatów – nic nie robią, dopóki wszystko działa. To uśpiony kod i uśpiony sprzęt, a właśnie w uśpionych częściach chowają się uszkodzenia ukryte.

Są trzy powody, żeby testować je celowo:

  1. Materiał dowodowy do dowodu bezpieczeństwa. Dowód bezpieczeństwa (safety case) deklaruje, że określone uszkodzenia są wykrywane i prowadzą do stanu bezpiecznego. Weryfikacja musi pokazać, że tak jest w rzeczywistej implementacji, a nie tylko na papierze.
  2. Pokrycie diagnostyczne. Wskaźniki sprzętowe w IEC 61508, EN 50129 i ISO 26262 zależą od tego, jaką część uszkodzeń niebezpiecznych wykrywa diagnostyka. Wstrzykiwanie uszkodzeń to jeden ze sposobów, by podeprzeć deklarowane wartości pokrycia.
  3. Czas. Wykrycie, które przychodzi za późno, oznacza, że mechanizm bezpieczeństwa zawiódł. Rzeczywisty czas wykrycia i reakcji pokaże dopiero test z prawdziwym uszkodzeniem.

Important

Kryterium zaliczenia testu rodzaju uszkodzenia nie brzmi „system działał dalej”. Brzmi: „uszkodzenie zostało wykryte, a system przeszedł w zdefiniowany stan bezpieczny w wymaganym czasie”.

Które rodzaje uszkodzeń testować

Tabela zbiera typowe rodzaje uszkodzeń wbudowanego systemu związanego z bezpieczeństwem oraz mechanizmy, które mają je wychwycić.

KategoriaPrzykładowe rodzaje uszkodzeńTypowy mechanizm bezpieczeństwa
Czujniki i wejściaWartość zablokowana, poza zakresem, dryf, zakłócenia, przerwa lub zwarcieKontrola zakresu i wiarygodności, porównanie redundantnych czujników
KomunikacjaPrzekłamanie, powtórzenie, usunięcie, wstawienie, zmiana kolejności, opóźnienie, podszywanie sięKod bezpieczeństwa, numery sekwencyjne, limity czasu, identyfikatory nadawcy (EN 50159)
Czas i wykonaniePrzekroczenie czasu zadania, zawieszenie programu, błędny przepływ sterowaniaWatchdog, monitorowanie przepływu programu, nadzór terminów
Pamięć i procesorPrzekłamania bitów w RAM, uszkodzony ROM, uszkodzona jednostka ALUTesty pamięci, CRC kodu i danych, autotesty procesora, ECC
Zasilanie i wyjściaZanik lub spadek napięcia, wyjście trwale aktywneMonitorowanie napięcia, odczyt zwrotny wyjść, stan bezpieczny przy braku zasilania
Dane i konfiguracjaBłędne lub niespójne dane konfiguracyjneWalidacja danych, sumy kontrolne, niezależna kontrola przygotowania danych

Wiersz „Komunikacja” odpowiada liście zagrożeń z EN 50159 – zwykłemu punktowi odniesienia dla komunikacji związanej z bezpieczeństwem w systemach kolejowych.

Inżynierowie systemów rozproszonych rozpoznają większość pozostałych wierszy. To te same rodzaje uszkodzeń co przekroczone limity czasu w sieci, zduplikowane komunikaty czy wyczerpanie zasobów – tyle że wymagana reakcja jest znacznie ostrzejsza.

Od FMEA do przypadków testowych

Analiza rodzajów i skutków uszkodzeń (FMEA) zawiera już większość tego, czego potrzebuje test ze wstrzykiwaniem uszkodzenia. Każdy wiersz podaje rodzaj uszkodzenia, jego skutek, sposób wykrycia i reakcję systemu. Skoro analiza deklaruje wykrycie, kampania testowa powinna je pokazać.

  +---------------+   +-----------------+   +-------------------+
  | Wiersz FMEA:  |-->| Wymaganie dla   |-->| Przypadek testowy |
  | rodzaj        |   | mechanizmu      |   | (wstrzyknięcie,   |
  | uszkodzenia   |   | bezpieczeństwa  |   |  obserwacja stanu |
  | + skutek      |   | (wykrycie,      |   |  bezpiecznego)    |
  +---------------+   |  reakcja, czas) |   +---------+---------+
          ^           +-----------------+             |
          |                                           |
          +-------------- wynik testu ----------------+
                (potwierdza lub koryguje FMEA)

Diagram pokazuje pętlę. Z FMEA wynikają wymagania dla mechanizmów bezpieczeństwa, z wymagań – przypadki testowe, a wyniki testów wracają do analizy.

Jeśli test pokaże, że uszkodzenie nie jest wykrywane albo jest wykrywane za późno, to znaczy, że FMEA była błędna. Poprawić trzeba argumentację bezpieczeństwa, a nie tylko kod.

Tabela pokazuje, jak z jednego wiersza FMEA powstaje przypadek testowy. Wartości są przykładowe.

Pole FMEATreśćElement przypadku testowego
Rodzaj uszkodzeniaCzujnik prędkości A zablokowany na ostatniej wartościUszkodzenie do wstrzyknięcia: zamrożenie wyjścia czujnika A
Skutek lokalnyZmierzona prędkość przestaje nadążać za rzeczywistąPobudzenie: zmiana rzeczywistej (symulowanej) prędkości
WykrywaniePorównanie czujnika A z czujnikiem BOczekiwane: zgłoszenie uszkodzenia z powodu rozbieżności
ReakcjaStan bezpieczny: polecenie hamowaniaOczekiwane: wydanie polecenia hamowania
Wymaganie czasoweWykrycie i reakcja w granicach FTTIPomiar: czas od wstrzyknięcia do polecenia hamowania

Pracując z FMEA i FTA przy kolejowych systemach związanych z bezpieczeństwem, przekonałem się, że najwięcej daje jeden nawyk: jawne utrzymywanie tego powiązania w narzędziu do zarządzania wymaganiami – rodzaj uszkodzenia → wymaganie bezpieczeństwa → przypadek testowy → wynik.

Luka w dowolnym miejscu tego łańcucha to luka w argumentacji bezpieczeństwa – i ktoś przy przeglądzie na pewno ją znajdzie.

Techniki wstrzykiwania uszkodzeń

Uszkodzenia można wstrzykiwać na różnych poziomach. Im bliżej rzeczywistego systemu, tym test jest bardziej realistyczny, ale też trudniej go kontrolować i powtarzać.

PoziomJak wstrzykuje się uszkodzeniaDo czego się nadaje
Model lub symulacjaUszkodzenia włączane w modelu systemu lub obiektu sterowaniaWczesne sprawdzenie koncepcji bezpieczeństwa i czasów reakcji
Moduł oprogramowaniaZaślepki i mocki zwracają błędy, niepoprawne wartości lub spóźnione odpowiedziKod defensywny, ścieżki obsługi błędów, walidacja wejść
Integracja oprogramowaniaPrzekłamane zmienne lub komunikaty przez hooki testowe albo debuggerInterfejsy między komponentami, obsługa błędów od początku do końca
Hardware-in-the-loopSymulatory sygnałów, wstrzykiwanie błędów na magistrali, uszkodzenia na wyprowadzeniachDiagnostyka, czasy, współdziałanie sprzętu i oprogramowania
SystemOdłączone przewody, przerwy w zasilaniu, przekłamane telegramyWalidacja dowodu bezpieczeństwa na rzeczywistej konfiguracji

Programowe wstrzykiwanie uszkodzeń

Na poziomie modułu i integracji najprostszy sposób to podmiana zależności na zaślepkę, która zachowuje się źle: zwraca kod błędu, wartość spoza zakresu albo nie zwraca nic. Test sprawdza wtedy, czy badany komponent reaguje zgodnie ze specyfikacją.

To ten sam pomysł co mockowanie niedziałającego klienta HTTP w usłudze webowej. Różni się oczekiwany wynik: zdefiniowana bezpieczna reakcja, a nie tylko „brak awarii”.

Hooki testowe (test hooks), które przekłamują dane w czasie działania, dają duże możliwości, ale to dodatkowy kod w produkcie. Usuń je z wersji wydawanej albo wykaż, że w eksploatacji nie da się ich uruchomić.

Wstrzykiwanie na poziomie sprzętu i systemu

Na stanowisku hardware-in-the-loop i na poziomie systemu uszkodzenia wprowadza się do rzeczywistych sygnałów: wejście czujnika wymuszone na stałą wartość, magistrala z przekłamanymi ramkami, chwilowy spadek napięcia zasilania.

Takie testy pokazują rzeczywiste czasy wykrycia i reakcji, z uwzględnieniem sprzętu i systemu operacyjnego. Test modułowy tego nie pokaże.

Co mówią normy

ISO 26262 wymienia testy ze wstrzykiwaniem uszkodzeń (fault injection testing) wśród metod weryfikacji sprzętu, oprogramowania i zintegrowanego systemu – im wyższy ASIL, tym mocniejsze zalecenie.

EN 50128 nie używa pojęcia inżynierii chaosu, ale jej katalog technik obejmuje techniki pokrewne, również dobierane według poziomu nienaruszalności bezpieczeństwa oprogramowania:

  • analizę wartości granicznych,
  • zgadywanie błędów (error guessing),
  • zasiewanie błędów (error seeding),
  • testy lawinowe i przeciążeniowe (avalanche/stress testing).

Pełne tabele technik omawiam w artykule CENELEC EN 50128, a o jej następczyni piszę w artykule EN 50128 a EN 50716.

Note

Zawsze sprawdzaj tabele technik w tym wydaniu normy, które obowiązuje w Twoim projekcie. Nazwy technik i poziomy zaleceń różnią się między normami i między wydaniami.

Inżynieria chaosu okiem inżyniera bezpieczeństwa

Inżynieria chaosu (chaos engineering) wyrosła z dużych usług internetowych. Zespoły celowo wyłączają instancje, dokładają opóźnienia albo zrywają połączenia sieciowe na produkcji, żeby sprawdzić, czy usługa pozostaje dostępna.

Metoda opiera się na hipotezie:

  1. Zdefiniuj normalny „stan ustalony” (steady state).
  2. Przewidź, że utrzyma się on po wystąpieniu określonego uszkodzenia.
  3. Wstrzyknij uszkodzenie i porównaj wynik z przewidywaniem.

Takie podejście oparte na hipotezie przydaje się także w inżynierii bezpieczeństwa. Różnica leży w samej hipotezie i w środowisku.

AspektInżynieria chaosu (usługi IT)Wstrzykiwanie uszkodzeń w systemach bezpieczeństwa
HipotezaUsługa pozostaje dostępna mimo uszkodzeniaSystem wykrywa uszkodzenie i na czas przechodzi w stan bezpieczny
ŚrodowiskoCzęsto produkcja, z ograniczonym zasięgiem skutkówLaboratorium, stanowisko testowe, HIL lub tor doświadczalny – nigdy ruch planowy
Dobór uszkodzeńLosowy lub na podstawie doświadczeniaSystematyczny, wyprowadzony z FMEA, FTA i rejestru zagrożeń
Miara sukcesuOdsetek błędów i opóźnienia w dopuszczalnych granicachWykrycie, stan bezpieczny i czas zgodne ze specyfikacją
Materiał dowodowyDashboardy i analizy poawaryjne (post-mortem)Specyfikacje testów, raporty i śledzenie powiązań na potrzeby dowodu bezpieczeństwa

Nigdy nie wstrzykuj uszkodzeń do systemu, który pełni funkcje bezpieczeństwa w eksploatacji

Wstrzykiwanie uszkodzeń do pociągu, pojazdu albo urządzeń zależnościowych w eksploatacji oznacza poleganie na tych samych mechanizmach bezpieczeństwa, które dopiero próbujesz sprawdzić. Rób to w kontrolowanym środowisku testowym, z niezależnymi zabezpieczeniami.

Z inżynierii chaosu warto przejąć przede wszystkim nastawienie: zakładaj, że istnieją rodzaje uszkodzeń, których nikt nie wpisał do FMEA, i szukaj ich celowo.

Losowe kampanie uszkodzeń na stanowisku testowym mogą uzupełniać systematyczne testy wyprowadzone z FMEA. Czasem ujawniają kombinacje, które analiza przeoczyła.

Testy oparte na właściwościach dla wymagań bezpieczeństwa

Testy oparte na przykładach sprawdzają pojedyncze scenariusze. Testy oparte na właściwościach (property-based testing) formułują właściwość, która musi być spełniona dla wszystkich danych wejściowych. Narzędzie generuje wtedy mnóstwo losowych wejść i sekwencji zdarzeń, szukając kontrprzykładu.

Wymagania bezpieczeństwa często są już zapisane właśnie w takiej postaci:

  • „Wyjście nigdy nie jest bardziej zezwalające, niż pozwalają na to wejścia”.
  • „Niepoprawny komunikat nigdy nie zmienia wartości używanej przez funkcję bezpieczeństwa”.
  • „Jeśli przez czas dłuższy niż limit nie nadszedł żaden poprawny komunikat, obowiązuje wartość bezpieczna”.

Poniższy szkic sprawdza dwie ostatnie właściwości na pythonowym modelu bezpiecznego odbiornika komunikatów z artykułu bezpieczeństwo w projektowaniu systemów. Korzysta z biblioteki Hypothesis. To przykład na poziomie modelu, a nie test rzeczywistego kodu w C.

from hypothesis import given, strategies as st

SAFE_SPEED = 0       # wartość najbardziej restrykcyjna
TIMEOUT_MS = 500


class Receiver:
    def __init__(self):
        self.last_seq = 0
        self.last_valid_ms = 0
        self.speed = SAFE_SPEED

    def on_msg(self, seq, speed, code_ok, now):
        if code_ok and seq > self.last_seq:
            self.last_seq = seq
            self.last_valid_ms = now
            self.speed = speed

    def cycle(self, now):
        if now - self.last_valid_ms > TIMEOUT_MS:
            self.speed = SAFE_SPEED


# Jedno zdarzenie: krok czasu, numer sekwencyjny, prędkość, kod bezpieczeństwa ok, dostarczony
events = st.lists(st.tuples(
    st.integers(0, 2000), st.integers(0, 100), st.integers(0, 300),
    st.booleans(), st.booleans()))


@given(events)
def test_receiver_safety_properties(evts):
    r, now = Receiver(), 0
    for dt, seq, speed, code_ok, delivered in evts:
        now += dt
        before = r.speed
        if delivered:
            r.on_msg(seq, speed, code_ok, now)
        if not (delivered and code_ok):
            # właściwość 1: niepoprawny lub utracony komunikat nigdy nie zmienia wartości
            assert r.speed == before
        r.cycle(now)
        if r.speed != SAFE_SPEED:
            # właściwość 2: wartość zezwalająca nigdy nie jest starsza niż limit czasu
            assert now - r.last_valid_ms <= TIMEOUT_MS

Komunikaty utracone, przekłamane, powtórzone i opóźnione powstają z tej samej strategii, więc jeden test przechodzi przez tysiące sekwencji uszkodzeń.

Gdy właściwość nie jest spełniona, narzędzie redukuje dane wejściowe do minimalnego kontrprzykładu. Często trudno o czytelniejszy opis wady projektowej.

Tip

Testy oparte na właściwościach uzupełniają wymagane przez normy testy oparte na wymaganiach i pomiary pokrycia. Nie zastępują ich. Najlepiej sprawdzają się we wczesnym badaniu przestrzeni stanów modelu lub modułu, jeszcze przed formalną kampanią testową.

Testowanie testów

Kampania wstrzykiwania uszkodzeń jest tyle warta, ile jej zdolność do wykazania błędu. Sprawdzić to pomagają dwie techniki:

  • Zasiewanie błędów (error seeding) lub testy mutacyjne: wprowadź do kodu znane błędy i sprawdź, czy zestaw testów je wykryje. Mutanty, które przetrwały, wskazują słabe testy.
  • Sprawdzenie negatywne mechanizmu bezpieczeństwa: w wersji testowej tymczasowo wyłącz diagnostykę i potwierdź, że odpowiadający jej test nie przechodzi. Jeśli nadal przechodzi, to tej diagnostyki wcale nie testował.

Od czego zacząć

Na początek nie potrzebujesz pełnego stanowiska HIL:

  1. Weź FMEA (albo rejestr zagrożeń) i wypisz wszystkie rodzaje uszkodzeń, dla których deklarowane jest wykrycie.
  2. Dla każdego deklarowanego wykrycia napisz jeden test – na najniższym poziomie, na którym uszkodzenie da się realistycznie wstrzyknąć.
  3. Sprawdzaj wykrycie, stan bezpieczny i czas, a nie tylko „brak awarii”.
  4. Powiąż każdy test z rodzajem uszkodzenia i wymaganiem bezpieczeństwa.
  5. Tam, gdzie przestrzeń stanów jest za duża na ręcznie pisane przypadki, dodaj losowe sekwencje uszkodzeń lub testy oparte na właściwościach.

Typowe błędy

  1. Testowanie tylko tego, czy funkcja działa. To właśnie mechanizmy bezpieczeństwa trzeba sprawdzać wstrzykiwaniem uszkodzeń – na ścieżce pozytywnej w ogóle się nie odzywają.
  2. Sprawdzanie „braku wyjątku” zamiast stanu bezpiecznego. Wymaganiem nie jest przetrwanie uszkodzenia, tylko przejście w zdefiniowany stan bezpieczny.
  3. Pomijanie czasu. Test bez pomiaru czasu nie wykaże, że reakcja mieści się w FTTI.
  4. Wyłącznie losowy dobór uszkodzeń. Kampanie losowe są tylko uzupełnieniem. Dowód bezpieczeństwa wymaga systematycznego pokrycia przeanalizowanych rodzajów uszkodzeń.
  5. Aktywne hooki do wstrzykiwania uszkodzeń w produkcie. Kod testowy, który potrafi przekłamać dane, nie może być osiągalny w eksploatacji.
  6. Brak sprzężenia zwrotnego. Niezaliczony test ze wstrzykiwaniem uszkodzenia oznacza, że błędna była również analiza. Aktualizuj FMEA, nie tylko kod.

Dalsza lektura