Software-Testing mit ChatGPT als Assistent

Seite 2: Testgenerierung mit ChatGPT

Inhaltsverzeichnis

Folgender TypeScript-Code mit dem Namengenerate-tests.ts verwendet ChatGPT, um Testcode basierend auf den Testfällen in der JSON-Datei zu generieren:

import * as fs from 'fs';
import * as path from 'path';
import { generateCypressTest, generateJasmineTest } from './your-chatgpt-wrapper';

(async () => {
// Load test cases from JSON file
const testCasesFile = 
  path.join(__dirname, 'user-service-tests.json');
const testCases = 
  JSON.parse(fs.readFileSync(testCasesFile, 'utf-8'));

// Generate Jasmine unit tests
const unitTests: string[] = [];

for (const testCase of testCases.unitTests) {
  const testCode = await generateJasmineTest(testCase);
  unitTests.push(testCode);
}

fs.writeFileSync('./path/to/unit-tests.spec.ts', 
                 unitTests.join('\n\n'));

// Generate Cypress E2E tests
const e2eTests: string[] = [];

for (const testCase of testCases.e2eTests) {
  const testCode = await generateCypressTest(testCase);
  e2eTests.push(testCode);
}

fs.writeFileSync('./cypress/integration/user-service.spec.ts',
                 e2eTests.join('\n\n'));

console.log('Test files generated successfully.');
})()

Der Code liest die Testfälle aus der JSON-Datei und verwendet die ChatGPT-Funktionen generateJasmineTest und generateCypressTest, um Jasmine-Unit-Tests und Cypress-E2E-Tests zu generieren. Anschließend schreibt er die generierten Testcodes in die Testdateien. Es bietet sich an, das Skript zur package.json-Datei hinzuzufügen, um es direkt mit npm run ausführen zu können:

"scripts": {
  ...
  "generate-tests": "ts-node ./generate-tests.ts"
}

Nun lassen sich die Tests des Projekts manuell oder automatisiert als Teil der Continuous Integration generieren oder aktualisieren:

npm run generate-tests

Die Ergebnisse, die der Code mithilfe von ChatGPT erstellt, können folgendermaßen aussehen – zunächst der Jasmine-Unit-Tests (`unit-tests.spec.ts`):

import { TestBed } from '@angular/core/testing';
import { UserService } from './user.service';

describe('UserService', () => {
  let service: UserService;

  beforeEach(() => {
    TestBed.configureTestingModule({});
    service = TestBed.inject(UserService);
  });

  it('should return user by id', async () => {
    const user = await service.getUserById(1);
    expect(user).toEqual({
      id: 1,
      name: 'John Doe',
      email: 'john.doe@example.com',
    });
  });

  it('should return all users', async () => {
    const users = await service.getUsers();
    expect(users).toEqual([
      {
        id: 1,
        name: 'John Doe',
        email: 'john.doe@example.com',
      },
      {
        id: 2,
        name: 'Jane Doe',
        email: 'jane.doe@example.com',
      },
    ]);
  });
});

Und schließlich der Cypress-E2E-Tests (`cypress/integration/user-service.spec.ts`):

describe('User Service E2E Tests', () => {
  it('should display the users list', () => {
    cy.visit('/users');

    cy.get('#user-1').should('be.visible');
    cy.get('#user-2').should('be.visible');
  });
});

Über die Skripte lassen sich auch Unit- oder E2E-Tests für andere Testframeworks neben Jasmine und Cypress generieren. Als Beispiele sollen die beiden folgenden Unit-Tests für mobile Betriebssysteme dienen. Der Testcode für iOS kann folgendermaßen aussehen:

import XCTest
@testable import YourAppModuleName

class UserTests: XCTestCase {

    var userService: UserService!

    override func setUpWithError() throws {
        userService = UserService()
    }

    override func tearDownWithError() throws {
        userService = nil
    }

    func testGetUserById() {
        let expectedOutput = 
          User(id: 1, name: "John Doe", 
               email: "john.doe@example.com")
        let user = userService.getUserById(1)

        XCTAssertEqual(user, expectedOutput)
    }

    func testGetUsers() {
        let expectedOutput = [
            User(id: 1, name: "John Doe", 
                 email: "john.doe@example.com"),
            User(id: 2, name: "Jane Doe", 
                email: "jane.doe@example.com")
        ]
        let users = userService.getUsers()

        XCTAssertEqual(users, expectedOutput)
    }
}

Folgender Testcode prüft Android-Anwendungen:

import org.junit.Assert.assertEquals
import org.junit.Test

class UserServiceTest {

    private val userService = UserService()

    @Test
    fun getUserById() {
        // Prepare expected output
        val expectedOutput = User(
            id = 1,
            name = "John Doe",
            email = "john.doe@example.com"
        )

        // Get user by id
        val user = userService.getUserById(1)

        // Compare actual and expected output
        assertEquals(expectedOutput, user)
    }

    @Test
    fun getUsers() {
        // Prepare expected output
        val expectedOutput = listOf(
            User(
                id = 1,
                name = "John Doe",
                email = "john.doe@example.com"
            ),
            User(
                id = 2,
                name = "Jane Doe",
                email = "jane.doe@example.com"
            )
        )

        // Get all users
        val users = userService.getUsers()

        // Compare actual and expected output
        assertEquals(expectedOutput, users)
    }
}

LLMs helfen nicht nur beim automatischen Erstellen von Tests, sondern lassen sich auch für Security-Testing verwenden. Teams können die LLM-Modelle beispielsweise einsetzen, um Code automatisch auf bekannte Schwachstellen zu überprüfen, Sicherheitslücken auf Basis von Beschreibungen zu identifizieren oder komplette Systeme und Systemlandschaften anhand bekannter Angriffsvektoren mit GPT-Agenten auf Schwachstellen abzuklopfen. Andreas Happe und Jürgen Cito haben dazu eine Abhandlung auf arXiv veröffentlicht.

Ein konkretes Szenario ist die Simulation von Angriffen auf REST APIs. Da diese in vielen Anwendungen und Diensten ein zentrales Element und zudem öffentlich erreichbar sind, sind sie ein beliebtes Ziel von Angriffen. LLMs können dabei helfen, simulierte Cyberangriffe auf REST APIs durchzuführen, um Schwachstellen wie unsichere Endpunkte, fehlende Authentifizierungsmechanismen oder Datenlecks zu identifizieren. Außerdem können ChatGPT und Co helfen, entdeckte Schwachstellen zu beheben oder die Umgebung zusätzlich abzusichern.

Darüber hinaus können Entwickler mit ChatGPT in natürlicher Sprache kommunizieren, um Best Practices im Bereich der Sicherheit zu erfragen oder um spezifische Lösungen für identifizierte Probleme zu erhalten. Indem man die Leistungsfähigkeit von LLMs mit bestehenden Sicherheitstools kombiniert, kann man einen mehrschichtigen Ansatz zur Sicherheitsautomatisierung schaffen, der die Effizienz erhöht und menschliche Fehler reduziert.