Praxisnahe testgetriebene Entwicklungsmethoden
Was ist testgetriebene Entwicklung?
Test Driven Development (TDD) bedeutet einfach, dass Sie zuerst die Tests schreiben. Bevor Sie eine Geschäftslogik schreiben, können Sie Erwartungen an den korrekten Code vorab festlegen. TDD trägt nicht nur dazu bei, sicherzustellen, dass Ihr Code korrekt ist, sondern hilft Ihnen auch dabei, kleinere Funktionen zu schreiben, Ihren Code umzugestalten, ohne die Funktionalität zu beeinträchtigen, und Ihr Problem besser zu verstehen.
In diesem Artikel werde ich einige Konzepte von TDD vorstellen, indem ich ein kleines Hilfsprogramm erstelle. Wir werden auch einige praktische Szenarien behandeln, in denen TDD Ihr Leben einfacher machen wird.
HTTP-Client mit TDD erstellen
Was wir bauen werden
Wir werden nach und nach einen einfachen HTTP-Client erstellen, der verschiedene HTTP-Verben abstrahiert. Um einen reibungslosen Ablauf des Refactorings zu gewährleisten, werden wir die TDD-Praktiken befolgen. Zum Testen werden wir Jasmine, Sinon und Karma verwenden. Kopieren Sie zunächst package.json, karma.conf.js und webpack.test.js aus dem Beispielprojekt oder klonen Sie das Beispielprojekt direkt aus dem GitHub-Repository.
Es hilft, wenn Sie verstehen, wie die neue Fetch-API funktioniert, aber diese Beispiele sollten leicht zu verstehen sein. Für Neulinge ist Fetch API eine bessere Alternative zu XMLHttpRequest. Es vereinfacht die Netzwerkinteraktion und funktioniert gut mit Promises.
VERPACKUNG ERHALTEN
Erstellen Sie zunächst eine leere Datei unter src/http.js und eine zugehörige Testdatei unter src/__tests__/http-test.js.
Lassen Sie uns eine Testumgebung für diesen Dienst einrichten.
import * as http from "../http.js"; import sinon from "sinon"; import * as fetch from "isomorphic-fetch"; describe("TestHttpService", () => { describe("Test success scenarios", () => { beforeEach(() => { stubedFetch = sinon.stub(window, "fetch"); window.fetch.returns(Promise.resolve(mockApiResponse())); function mockApiResponse(body = {}) { return new window.Response(JSON.stringify(body), { status: 200, headers: { "Content-type": "application/json" } }); } }); }); });
Wir verwenden hier Jasmine und Sinon – Jasmine zum Definieren von Testszenarien, Sinon für Behauptungen und die Überwachung von Objekten. (Jasmine hat seine eigene Methode zum Überwachen und Stubbing von Tests, aber ich bevorzuge Sinons API.)
Der obige Code ist selbsterklärend. Vor jedem Testlauf kapern wir den Aufruf der Fetch-API, da kein Server verfügbar ist, und geben ein simuliertes Promise-Objekt zurück. Das Ziel besteht hier darin, einen Unit-Test durchzuführen, ob die Fetch-API mit den richtigen Parametern aufgerufen wird, und zu sehen, ob der Wrapper etwaige Netzwerkfehler korrekt behandelt.
Beginnen wir mit dem fehlgeschlagenen Testfall:
describe("Test get requests", () => { it("should make a GET request", done => { http.get(url).then(response => { expect(stubedFetch.calledWith(`${url}`)).toBeTruthy(); expect(response).toEqual({}); done(); }); }); });
Durch Aufruf der karma start
启动测试运行程序。现在测试显然会失败,因为 http
中没有 get
-Methode. Lassen Sie uns dieses Problem beheben.
const status = response => { if (response.ok) { return Promise.resolve(response); } return Promise.reject(new Error(response.statusText)); }; export const get = (url, params = {}) => { return fetch(url) .then(status); };
Wenn Sie den Test jetzt ausführen, wird eine fehlgeschlagene Antwort angezeigt: 预期 [object Response] 等于 Object({ })
。响应是一个 Stream 对象。顾名思义,流对象都是一个数据流。要从流中获取数据,您需要首先使用流的一些辅助方法来读取流。现在,我们可以假设流是 JSON 并通过调用 response.json()
Deserialisieren.
const deserialize = response => response.json(); export const get = (url, params = {}) => { return fetch(url) .then(status) .then(deserialize) .catch(error => Promise.reject(new Error(error))); };
Unsere Testsuite sollte jetzt grün sein.
Abfrageparameter hinzufügen
Bisher get
方法只是进行了一个简单的调用,没有任何查询参数。让我们编写一个失败的测试,看看它如何处理查询参数。如果我们传递 { users: [1, 2], limit: 50, isDetailed: false }
作为查询参数,我们的 HTTP 客户端应该对 /api 进行网络调用/v1/users/?users=1&users=2&limit=50&isDetailed=false
it("should serialize array parameter", done => { const users = [1, 2]; const limit = 50; const isDetailed = false; const params = { users, limit, isDetailed }; http .get(url, params) .then(response => { expect(stubedFetch.calledWith(`${url}?isDetailed=false&limit=50&users=1&users=2/`)).toBeTruthy(); done(); }) });
-Methode, um die Abfrageparameter zu verarbeiten. get
import { stringify } from "query-string"; export const get = (url, params) => { const prefix = url.endsWith('/') ? url : `${url}/`; const queryString = params ? `?${stringify(params)}/` : ''; return fetch(`${prefix}${queryString}`) .then(status) .then(deserializeResponse) .catch(error => Promise.reject(new Error(error))); };
Hier habe ich die Abfragezeichenfolgenbibliothek verwendet – eine nette kleine Hilfsbibliothek, die bei verschiedenen Abfrageparameterszenarien hilft.
Umgang mit Mutationen
GET ist wahrscheinlich die am einfachsten zu implementierende HTTP-Methode. GET ist idempotent und sollte nicht für Mutationen verwendet werden. POST bedeutet normalerweise, einige Datensätze auf dem Server zu aktualisieren. Das bedeutet, dass POST-Anfragen standardmäßig einige Sicherheitsmaßnahmen erfordern, wie z. B. CSRF-Tokens. Mehr dazu im nächsten Abschnitt.
Beginnen wir mit der Erstellung eines Tests für eine einfache POST-Anfrage:
describe(`Test post requests`, () => { it("should send request with custom headers", done => { const postParams = { users: [1, 2] }; http.post(url, postParams, { contentType: http.HTTP_HEADER_TYPES.text }) .then(response => { const [uri, params] = [...stubedFetch.getCall(0).args]; expect(stubedFetch.calledWith(`${url}`)).toBeTruthy(); expect(params.body).toEqual(JSON.stringify(postParams)); expect(params.headers.get("Content-Type")).toEqual(http.HTTP_HEADER_TYPES.text); done(); }); }); });
. options
属性,您可以在其中定义标头、正文,以及最重要的 method
。该方法描述了 HTTP 动词,在本例中为 "post"
export const HTTP_HEADER_TYPES = { json: "application/json", text: "application/text", form: "application/x-www-form-urlencoded", multipart: "multipart/form-data" }; export const post = (url, params) => { const headers = new Headers(); headers.append("Content-Type", HTTP_HEADER_TYPES.json); return fetch(url, { headers, method: "post", body: JSON.stringify(params), }); };
Methode sehr primitiv. Es unterstützt nichts anderes als JSON-Anfragen. post
替代内容类型和 CSRF 令牌
让我们允许调用者决定内容类型,并将 CSRF 令牌投入战斗。根据您的要求,您可以将 CSRF 设为可选。在我们的用例中,我们将假设这是一个选择加入功能,并让调用者确定是否需要在标头中设置 CSRF 令牌。
为此,首先将选项对象作为第三个参数传递给我们的方法。
it("should send request with CSRF", done => { const postParams = { users: [1, 2 ] }; http.post(url, postParams, { contentType: http.HTTP_HEADER_TYPES.text, includeCsrf: true }).then(response => { const [uri, params] = [...stubedFetch.getCall(0).args]; expect(stubedFetch.calledWith(`${url}`)).toBeTruthy(); expect(params.body).toEqual(JSON.stringify(postParams)); expect(params.headers.get("Content-Type")).toEqual(http.HTTP_HEADER_TYPES.text); expect(params.headers.get("X-CSRF-Token")).toEqual(csrf); done(); }); });
当我们提供 options
和 {contentType: http.HTTP_HEADER_TYPES.text,includeCsrf: true
时,它应该相应地设置内容标头和 CSRF 标头。让我们更新 post
函数以支持这些新选项。
export const post = (url, params, options={}) => { const {contentType, includeCsrf} = options; const headers = new Headers(); headers.append("Content-Type", contentType || HTTP_HEADER_TYPES.json()); if (includeCsrf) { headers.append("X-CSRF-Token", getCSRFToken()); } return fetch(url, { headers, method: "post", body: JSON.stringify(params), }); }; const getCsrfToken = () => { //This depends on your implementation detail //Usually this is part of your session cookie return 'csrf' }
请注意,获取 CSRF 令牌是一个实现细节。通常,它是会话 cookie 的一部分,您可以从那里提取它。我不会在本文中进一步讨论它。
您的测试套件现在应该很满意。
编码形式
我们的 post
方法现在已经成型,但是在发送正文时仍然很简单。您必须针对每种内容类型以不同的方式处理数据。处理表单时,我们应该在通过网络发送数据之前将数据编码为字符串。
it("should send a form-encoded request", done => { const users = [1, 2]; const limit = 50; const isDetailed = false; const postParams = { users, limit, isDetailed }; http.post(url, postParams, { contentType: http.HTTP_HEADER_TYPES.form, includeCsrf: true }).then(response => { const [uri, params] = [...stubedFetch.getCall(0).args]; expect(stubedFetch.calledWith(`${url}`)).toBeTruthy(); expect(params.body).toEqual("isDetailed=false&limit=50&users=1&users=2"); expect(params.headers.get("Content-Type")).toEqual(http.HTTP_HEADER_TYPES.form); expect(params.headers.get("X-CSRF-Token")).toEqual(csrf); done(); }); });
让我们提取一个小辅助方法来完成这项繁重的工作。基于 contentType
,它对数据的处理方式有所不同。
const encodeRequests = (params, contentType) => { switch (contentType) { case HTTP_HEADER_TYPES.form: { return stringify(params); } default: return JSON.stringify(params); } } export const post = (url, params, options={}) => { const {includeCsrf, contentType} = options; const headers = new Headers(); headers.append("Content-Type", contentType || HTTP_HEADER_TYPES.json); if (includeCsrf) { headers.append("X-CSRF-Token", getCSRFToken()); } return fetch(url, { headers, method="post", body: encodeRequests(params, contentType || HTTP_HEADER_TYPES.json) }).then(deserializeResponse) .catch(error => Promise.reject(new Error(error))); };
看看那个!即使在重构核心组件之后,我们的测试仍然可以通过。
处理 PATCH 请求
另一个常用的 HTTP 动词是 PATCH。现在,PATCH 是一个变异调用,这意味着这两个操作的签名非常相似。唯一的区别在于 HTTP 动词。通过简单的调整,我们可以重用为 POST 编写的所有测试。
['post', 'patch'].map(verb => { describe(`Test ${verb} requests`, () => { let stubCSRF, csrf; beforeEach(() => { csrf = "CSRF"; stub(http, "getCSRFToken").returns(csrf); }); afterEach(() => { http.getCSRFToken.restore(); }); it("should send request with custom headers", done => { const postParams = { users: [1, 2] }; http[verb](url, postParams, { contentType: http.HTTP_HEADER_TYPES.text }) .then(response => { const [uri, params] = [...stubedFetch.getCall(0).args]; expect(stubedFetch.calledWith(`${url}`)).toBeTruthy(); expect(params.body).toEqual(JSON.stringify(postParams)); expect(params.headers.get("Content-Type")).toEqual(http.HTTP_HEADER_TYPES.text); done(); }); }); it("should send request with CSRF", done => { const postParams = { users: [1, 2 ] }; http[verb](url, postParams, { contentType: http.HTTP_HEADER_TYPES.text, includeCsrf: true }).then(response => { const [uri, params] = [...stubedFetch.getCall(0).args]; expect(stubedFetch.calledWith(`${url}`)).toBeTruthy(); expect(params.body).toEqual(JSON.stringify(postParams)); expect(params.headers.get("Content-Type")).toEqual(http.HTTP_HEADER_TYPES.text); expect(params.headers.get("X-CSRF-Token")).toEqual(csrf); done(); }); }); it("should send a form-encoded request", done => { const users = [1, 2]; const limit = 50; const isDetailed = false; const postParams = { users, limit, isDetailed }; http[verb](url, postParams, { contentType: http.HTTP_HEADER_TYPES.form, includeCsrf: true }).then(response => { const [uri, params] = [...stubedFetch.getCall(0).args]; expect(stubedFetch.calledWith(`${url}`)).toBeTruthy(); expect(params.body).toEqual("isDetailed=false&limit=50&users=1&users=2"); expect(params.headers.get("Content-Type")).toEqual(http.HTTP_HEADER_TYPES.form); expect(params.headers.get("X-CSRF-Token")).toEqual(csrf); done(); }); }); }); });
类似地,我们可以通过使动词可配置来重用当前的 post
方法,并重命名方法名称以反映通用的内容。
const request = (url, params, options={}, method="post") => { const {includeCsrf, contentType} = options; const headers = new Headers(); headers.append("Content-Type", contentType || HTTP_HEADER_TYPES.json); if (includeCsrf) { headers.append("X-CSRF-Token", getCSRFToken()); } return fetch(url, { headers, method, body: encodeRequests(params, contentType) }).then(deserializeResponse) .catch(error => Promise.reject(new Error(error))); }; export const post = (url, params, options = {}) => request(url, params, options, 'post');
现在我们所有的 POST 测试都已通过,剩下的就是为 patch
添加另一个方法。
export const patch = (url, params, options = {}) => request(url, params, options, 'patch');
很简单,对吧?作为练习,尝试自行添加 PUT 或 DELETE 请求。如果您遇到困难,请随时参考该存储库。
何时进行 TDD?
社区对此存在分歧。有些程序员一听到 TDD 这个词就逃跑并躲起来,而另一些程序员则靠它生存。只需拥有一个好的测试套件,您就可以实现 TDD 的一些有益效果。这里没有正确的答案。这完全取决于您和您的团队对您的方法是否满意。
根据经验,我使用 TDD 来解决需要更清晰的复杂、非结构化问题。在评估一种方法或比较多种方法时,我发现预先定义问题陈述和边界很有帮助。它有助于明确您的功能需要处理的需求和边缘情况。如果案例数量太多,则表明您的程序可能做了太多事情,也许是时候将其拆分为更小的单元了。如果需求很简单,我会跳过 TDD,稍后添加测试。
总结
关于这个话题有很多噪音,而且很容易迷失方向。如果我能给你一些临别建议的话:不要太担心 TDD 本身,而要关注基本原则。这一切都是为了编写干净、易于理解、可维护的代码。 TDD 是程序员工具带中的一项有用技能。随着时间的推移,您会对何时应用此方法产生直觉。
感谢您的阅读,请在评论部分告诉我们您的想法。
Das obige ist der detaillierte Inhalt vonPraxisnahe testgetriebene Entwicklungsmethoden. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Heiße KI -Werkzeuge

Undresser.AI Undress
KI-gestützte App zum Erstellen realistischer Aktfotos

AI Clothes Remover
Online-KI-Tool zum Entfernen von Kleidung aus Fotos.

Undress AI Tool
Ausziehbilder kostenlos

Clothoff.io
KI-Kleiderentferner

AI Hentai Generator
Erstellen Sie kostenlos Ai Hentai.

Heißer Artikel

Heiße Werkzeuge

Notepad++7.3.1
Einfach zu bedienender und kostenloser Code-Editor

SublimeText3 chinesische Version
Chinesische Version, sehr einfach zu bedienen

Senden Sie Studio 13.0.1
Leistungsstarke integrierte PHP-Entwicklungsumgebung

Dreamweaver CS6
Visuelle Webentwicklungstools

SublimeText3 Mac-Version
Codebearbeitungssoftware auf Gottesniveau (SublimeText3)

Heiße Themen



Verwendung von Python-Skripten zur Implementierung automatisierter Tests in der Linux-Umgebung Angesichts der schnellen Entwicklung der Softwareentwicklung spielen automatisierte Tests eine entscheidende Rolle bei der Sicherstellung der Softwarequalität und der Verbesserung der Entwicklungseffizienz. Als einfache und benutzerfreundliche Programmiersprache verfügt Python über eine hohe Portabilität und Entwicklungseffizienz und wird häufig in automatisierten Tests eingesetzt. In diesem Artikel wird erläutert, wie Sie mit Python automatisierte Testskripte in einer Linux-Umgebung schreiben, und es werden spezifische Codebeispiele bereitgestellt. Umgebungsvorbereitung für die Automatisierung in einer Linux-Umgebung

In der modernen Softwareentwicklung ist die kontinuierliche Integration (CI) zu einer wichtigen Methode zur Verbesserung der Codequalität und Entwicklungseffizienz geworden. Unter diesen ist Jenkins ein ausgereiftes und leistungsstarkes Open-Source-CI-Tool, das sich besonders für PHP-Anwendungen eignet. Der folgende Inhalt befasst sich mit der Verwendung von Jenkins zur Implementierung der kontinuierlichen PHP-Integration und stellt spezifischen Beispielcode und detaillierte Schritte bereit. Jenkins-Installation und -Konfiguration Zunächst muss Jenkins auf dem Server installiert werden. Laden Sie einfach die neueste Version von der offiziellen Website herunter und installieren Sie sie. Nach Abschluss der Installation sind einige grundlegende Konfigurationen erforderlich, einschließlich der Einrichtung eines Administratorkontos, der Plug-in-Installation und der Auftragskonfiguration. Erstellen Sie einen neuen Job. Klicken Sie im Jenkins-Dashboard auf die Schaltfläche „Neuer Job“. Wählen Sie „Freigaben“.

Was sind die Best Practices für PHP-Paketierung und -Bereitstellung? Mit der rasanten Entwicklung der Internet-Technologie PHP als Open-Source-Programmiersprache, die in der Website-Entwicklung weit verbreitet ist, müssen immer mehr Entwickler die Effizienz und Stabilität bei der Projektbereitstellung verbessern. In diesem Artikel werden mehrere Best Practices für die PHP-Paketierung und -Bereitstellung vorgestellt und relevante Codebeispiele bereitgestellt. Verwenden Sie Tools zur Versionskontrolle. Tools zur Versionskontrolle wie Git, SVN usw. können Entwicklern dabei helfen, Codeänderungen effektiv zu verwalten. Verwenden Sie Versionskontrolltools, um Code einfach zu verfolgen und zurückzusetzen und sicherzustellen, dass jede Bereitstellung erfolgt

Im aktuellen Softwareentwicklungsprozess sind kontinuierliche Integration (ContinuousIntegration) und kontinuierliche Bereitstellung (ContinuousDelivery) zu Schlüsselpraktiken für Entwicklungsteams geworden, um die Produktqualität zu verbessern und die Bereitstellung zu beschleunigen. Egal, ob Sie ein großes Softwareunternehmen oder ein kleines Team sind, Sie können von beiden Bereichen profitieren. In diesem Artikel finden C#-Entwickler einige Vorschläge zu kontinuierlichen Integrations- und Continuous-Delivery-Praktiken. Automatisierte Builds und Tests Automatisierte Builds und Tests sind die Grundlage der kontinuierlichen Integration. machen

Mit Webman eine kontinuierliche Integration und Bereitstellung von Websites erreichen Mit der rasanten Entwicklung des Internets ist die Arbeit an der Website-Entwicklung und -Wartung immer komplexer geworden. Um die Entwicklungseffizienz zu verbessern und die Website-Qualität sicherzustellen, sind kontinuierliche Integration und Bereitstellung zu einer wichtigen Wahl geworden. In diesem Artikel werde ich vorstellen, wie das Webman-Tool zur Implementierung der kontinuierlichen Integration und Bereitstellung der Website verwendet wird, und einige Codebeispiele beifügen. 1. Was ist Webman? Webman ist ein Java-basiertes Open-Source-Tool für kontinuierliche Integration und Bereitstellung

Titel: Codeabdeckungsanalyse und Beispiele in GitLab Continuous Integration Einführung: Da die Softwareentwicklung immer komplexer wird, ist die Codeabdeckungsanalyse zu einem wichtigen Indikator für die Bewertung der Qualität von Softwaretests geworden. Der Einsatz kontinuierlicher Integration zur Durchführung einer Codeabdeckungsanalyse kann Entwicklungsteams dabei helfen, ihre Codequalität in Echtzeit zu überwachen und die Effizienz der Softwareentwicklung zu verbessern. In diesem Artikel wird erläutert, wie Sie eine Codeabdeckungsanalyse für die kontinuierliche Integration in GitLab durchführen, und es werden spezifische Codebeispiele bereitgestellt. 1. Codeabdeckungsanalyse in GitLab 1.1 Codeabdeckung

Bei der PHP-Entwicklung ist die Aufrechterhaltung der Codequalität von entscheidender Bedeutung, um die Zuverlässigkeit, Wartbarkeit und Sicherheit der Software zu verbessern. Durch die kontinuierliche Überwachung der Codequalität werden Probleme proaktiv identifiziert, frühzeitige Korrekturen gefördert und verhindert, dass sie in die Produktion gelangen. In diesem Artikel erfahren Sie, wie Sie mit Jenkins und SonarQube eine kontinuierliche Überwachungspipeline für ein PHP-Projekt einrichten. Jenkins: Continuous Integration Server Jenkins ist ein Open-Source-Continuous-Integration-Server, der den Build-, Test- und Bereitstellungsprozess automatisiert. Es ermöglicht Entwicklern, Jobs einzurichten, die regelmäßig ausgelöst werden und eine Reihe von Aufgaben ausführen. Für PHP-Projekte können wir Jenkins-Jobs einrichten, um die folgenden Aufgaben auszuführen: Code aus dem Versionskontrollsystem auschecken

So verwenden Sie React und Jenkins zum Erstellen von Front-End-Anwendungen mit kontinuierlicher Integration und kontinuierlicher Bereitstellung. Einführung: In der heutigen Internetentwicklung sind kontinuierliche Integration und kontinuierliche Bereitstellung zu wichtigen Mitteln für Entwicklungsteams geworden, um die Effizienz zu verbessern und die Produktqualität sicherzustellen. Als beliebtes Front-End-Framework kann React uns in Kombination mit Jenkins, einem leistungsstarken Tool für die kontinuierliche Integration, eine praktische und effiziente Lösung für die Erstellung von Front-End-Anwendungen für kontinuierliche Integration und kontinuierliche Bereitstellung bieten. In diesem Artikel wird detailliert beschrieben, wie React und Jenkins zur Unterstützung verwendet werden
