分支覆盖率 代码覆盖率

(由Adobe Stock Photo许可)

互联网上现在有很多建议说100%的覆盖率不是一个值得的目标。

我非常不同意。

通常,难以测试的代码是需要重构的信号。

我知道了。 几年前,我很讨厌测试。 我以为这只会让我移动得更慢。

当我开始编码时,这并不是人们经常做的事情。 如果是这样,通常是由一个单独的质量检查小组负责。 几年前,尽管它成为一个真正的热门话题。 面试开始期望候选人知道如何编写测试,并且越来越多的组织将其作为质量计划从上至下进行推广。

我一直努力在比赛中处于领先地位,因此我决定走进面试,并说“测试并不是我真正的强项”不再是一个好看的样子,所以我决定我将获得所有比赛的100%覆盖率从那时起我的测试。

当时,我不确定自己会从中获得什么好处,或者是否真的有任何好处。

现在, 老实说 ,我不会回去。 当某件事破坏了覆盖率100%的代码库时,您的测试很可能会告诉您确切的位置和方式。

这并不是说单元测试就是您所需要的。 不是。 但是在我看来,未经测试的代码也不是一个好选择。

回到我身边,回到我也不相信测试覆盖范围的好处的时代。

第1部分:学习Lingo

当时,交易工具是摩卡咖啡 , 西农和柴的结合体 。 Mocha是测试运行程序,sinon提供了创建“模拟”和“间谍”的功能,而chai是一个声明库,因此您可以以人类语言友好的方式键入声明。

(这是间谍吗?)

我基本上不知道这意味着什么。 在我变得有效之前,首先要做的就是学习语言。

所以,第一件事是第一位- 地狱是间谍还是嘲笑?

尽管首先想到的是詹姆斯·邦德或伊桑·亨特。 尽管这不是一个可怕的隐喻,但这绝对不是我们在这里谈论的内容。

在阅读了一些文档之后,我最终了解到间谍是一种功能,已通过测试框架进行了修改,以提供有关其用法的元信息。 它在监视。 Kinda喜欢人们如何利用Apple最近的FaceTime Bug监视您。 有点像詹姆斯·邦德。

模拟与间谍相似,但经过了更多修改。 除了提供并跟踪如何使用特定功能外,它还将其行为更改为可预测的。

我还了解到有几种测试类型。 不限于三种最常见的:单元测试,集成测试和E2E测试。

当我们进行“单元测试”时,这意味着我们需要能够将代码分解为各个单元。 该特定单元之外的任何内容都是要被嘲弄的候选对象,例如其他功能或整个模块。 开玩笑是我选择单元测试的工具。 单元测试是衡量覆盖率的唯一测试类型。

当我们进行集成测试时,我们正在测试我们的软件与其他软件的集成,例如通过卡夫卡传递一条消息的测试,该消息应该让我们的服务接收到该消息,然后可以在数据库中找到该消息的结果。 在创建集成测试时,我通常也喜欢Jest。

端到端测试有点像使用您的应用程序的机器人。 您可以对其进行编程,以将其加载到浏览器中的网站,单击内容,并确保一切都能从用户角度正常运行。 赛普拉斯是我在该领域最喜欢的工具,但是当我学习时,这种工具就不存在了。 Selenium是当今的佼佼者,老实说,这是一个足够大的领域,我很乐意让QA自动化工程师来处理这一部分。

现在掌握了新知识就成了困难的部分:将其付诸实践。

我花了几个月的时间来确保我编写的每段代码都具有测试覆盖率。 我承认,起初很难。 我在StackOverflow上花费了大量时间来查找模拟和间谍示例。 到最后,我发现自己对代码的信心大大提高了。

另一个好处是,如果发生故障,我的测试通常会告诉我确切的位置。 当其他工程师对我所做的代码进行更改时,我可以更快地对其进行检查。 当重要的API发生更改时,测试失败会通知人们,并Swift对其进行了更新或重新考虑了他们的更改。

不仅如此,我开始编写更好的代码。 我了解到,通常情况下,如果难以测试或难以完全覆盖某些内容,通常意味着我编写的代码不太好,并且可以对其进行重构,从而获得更加可维护和灵活的API。 为此,尝试达到100%的覆盖率鼓励了我将匿名函数提取到命名函数中,并了解许多重构中的部分应用程序和依赖项注入。

在完成集成测试后,我甚至放弃了GitFlow进行基于主干的开发。 几年前,我以为精通大师是一件疯狂的事情,现在我每天要由近15名工程师组成的团队来做。

相关阅读 : 我有一个自白。 我致力于掌握

第2部分:以身作则

大约在我对新的测试堆栈充满信心的时候,另一种工具被推向市场,许多人声称这使单元测试变得更加简单:Jest。

Jest是Facebook率先推出的自动测试框架。

Jest将我以前使用的库浓缩到一个统一的框架(该框架是一个测试运行器)以及一组用于模拟,间谍和断言的API方面,做得非常出色。 除了为您的所有单元测试需求提供单个库之外,Jest在简化某些概念和模式以及强大而简单的模拟方面也做得很出色。

因为我认为Jest更易于使用和理解,所以我将继续以Jest为例。

如果您只是和我一起写这篇文章,那很好-到目前为止您所读的内容都将独立存在。 但是,我一直在记录使用带有Streaming SSR的Parcel构建React应用程序的过程,本文将在最后一部分继续。

在下面链接的上一篇文章中,我展示了如何设置具有代码覆盖率的Jest,并在下一篇文章中说,我将展示如何使覆盖率达到100%。

相关阅读 : 增强Node.js的代码质量

我认为证明100%覆盖率的最佳方法是展示如何到达那里。 在整个过程中,我们可能会发现几个可以重构代码以使其更具可测试性的地方。 因此,我将继续我的工作,将这个项目的覆盖率提高到100%,并说明要进行哪些重构,在哪里使用部分应用程序和依赖项注入以及在难以获得覆盖率的过程中要模拟什么。

所以...让我们开始吧。 这是我将要从事的项目:

GitHub链接patrickleet / streaming-ssr-react-styled-components

该项目在app文件夹中有一个react app,在服务器文件夹中有SSR逻辑。 让我们从应用程序测试开始。

应用测试

在上一篇文章中 ,在配置了Jest之后,我开始了对一个简单组件的简单测试。 我有几个同样简单的React组件。

这是功能组件真正强大的原因之一。 函数比类容易测试。 他们没有状态,而是有输入和输出。 给定输入X,它们的输出为Y。存在状态时,可以将其存储在组件的外部。

在这方面,新的React Hooks API很不错,因为它鼓励制作功能组件,并具有易于模拟的机制来为组件提供状态。 Redux在测试方面提供了相同的好处。

让我们从淘汰其余的简单组件开始。 我们基本上只需要渲染它们,也许检查是否渲染了一些重要的信息。

我通常将代码内联在文章中,但是这些测试中并没有什么新内容,因此我决定链接到实际的提交,只显示一个完整的示例:

让我们看一下“关于”页面:

import React from 'react'
import Helmet from 'react-helmet-async'
import Page from '../components/Page'
const About = () => (< Page > <Helmet><title>About Page</title></Helmet><div>This is the about page</div> </ Page >
)
export default About

它是测试:

import React from 'react'
import { shallow } from 'enzyme'
import About from 'app/pages/About.jsx'
describe( 'app/pages/About.jsx' , () => {it( 'renders About page' , () => {expect(About).toBeDefined()const tree = shallow( < About /> )expect(tree.find('Page')).toBeDefined()expect(tree.find('Helmet').find('title').text()).toEqual('About Page')expect(tree.find('div').text()).toEqual('This is the about page')})
})

以下提交中的所有测试都非常相似:

  • 修复:测试页面
  • 修复:组件测试
  • 测试:样式组件渲染测试

如您所见,只需确保我们的组件渲染足以使这些组件获得100%的覆盖率即可。 更详细的交互最好留给E2E测试,这不在本文的讨论范围之内。

下一个组件app/App.jsx稍微复杂一些。 编写渲染测试后,您会注意到路由器中仍然存在无法访问的匿名函数,用于渲染“关于”页面。

为了访问和测试它,我们想做一个小的重构,将函数提取为命名函数,以便我们可以导出它并进行测试。

现在很容易测试:

因为上面的“关于”页面有另一组测试,所以我们将保留其更具体的测试,并且只需要检查它是否在此处呈现即可。

这样,在我们的应用程序中剩下要测试的唯一文件是app/client.js ,然后我们可以继续进行服务器端测试。

让我们看一下代码:

import React from 'react'
import ReactDOM from 'react-dom'
import { HelmetProvider } from 'react-helmet-async'
import { BrowserRouter } from 'react-router-dom'
import { rehydrateMarks } from 'react-imported-component'
import importedComponents from './imported' // eslint-disable-line
import App from './App'
const element = document .getElementById( 'app' )
const app = (< HelmetProvider > <BrowserRouter><App /></BrowserRouter> </ HelmetProvider >
)
// In production, we want to hydrate instead of render
// because of the server-rendering
if (process.env.NODE_ENV === 'production' ) {// rehydrate the bundle marksrehydrateMarks().then( () => {ReactDOM.hydrate(app, element)})
} else {ReactDOM.render(app, element)
}
// Enable Hot Module Reloading
if ( module .hot) {module .hot.accept()
}

我注意到的第一件事是依赖全局变量documentprocessmodule 。 第二件事是什么也没有导出,因此使用不同的输入可能很难多次运行。

我们可以通过一些重构来解决这个问题:

  1. 将所有逻辑包装到我们可以导出的函数中。 此函数将接受带有所有依赖项的options对象。 这称为依赖注入 。 如果选择的话,这将使我们能够轻松地传递一堆东西的模拟版本。
  2. 重新水化后,我们在生产模式下有一个匿名函数,应将其提取为命名函数。

我们还将要模拟一些外部模块: react-domreact-imported-componentapp/imported.js 。 模块本身就是依赖注入的一种形式。

首先,这里是新重构的文件,其中的更改以粗体显示:

import React from 'react'
import ReactDOM from 'react-dom'
import { HelmetProvider } from 'react-helmet-async'
import { BrowserRouter } from 'react-router-dom'
import { rehydrateMarks } from 'react-imported-component'
import importedComponents from './imported' // eslint-disable-line
import App from './App'
// use "partial application" to make this easy to test
export const hydrate = ( app, element ) => () => {ReactDOM.hydrate(app, element)
}
export const start = ({ isProduction,document ,module ,hydrate}) => {const element = document .getElementById( 'app' )const app = (< HelmetProvider > <BrowserRouter><App /></BrowserRouter> </ HelmetProvider >)// In production, we want to hydrate instead of render// because of the server-renderingif (isProduction) {// rehydrate the bundle marks from imported-components, // then rehydrate the react apprehydrateMarks().then(hydrate(app, element))} else {ReactDOM.render(app, element)}// Enable Hot Module Reloadingif ( module .hot) {module .hot.accept()}
}
const options = {isProduction : process.env.NODE_ENV === 'production' ,document : document ,module : module ,hydrate
}
start(options)

现在,我们实际上可以使用各种选项访问和测试启动,以及独立于启动逻辑测试水合物。

测试时间有点长,所以我在网上添加了注释以解释发生了什么。 这是对该文件的测试:

import React from 'react'
import fs from 'fs'
import path from 'path'
import { start, hydrate } from 'app/client'
import { JSDOM } from "jsdom"
jest.mock( 'react-dom' )
jest.mock( 'react-imported-component' )
jest.mock( 'app/imported.js' )
// mock DOM with actual index.html contents
const pathToIndex = path.join(process.cwd(), 'app' , 'index.html' )
const indexHTML = fs.readFileSync(pathToIndex).toString()
const DOM = new JSDOM(indexHTML)
const document = DOM.window.document
// this doesn't contribute to coverage, but we
// should know if it changes as it would
// cause our app to break
describe( 'app/index.html' , () => {it( 'has element with id "app"' , () => {const element = document .getElementById( 'app' )expect(element.id).toBe( 'app' )})
})
describe( 'app/client.js' , () => {// Reset counts of mock calls after each testafterEach( () => {jest.clearAllMocks()})describe( '#start' , () => {it( 'renders when in development and accepts hot module reloads' , () => {// this is mocked above, so require gets the mock version// so we can see if its functions are calledconst ReactDOM = require ( 'react-dom' )// mock module.hotconst module = {hot : {accept : jest.fn()}}// mock optionsconst options = {isProduction : false ,module ,document}start(options)expect(ReactDOM.render).toBeCalled()expect( module .hot.accept).toBeCalled()})it( 'hydrates when in production does not accept hot module reloads' , () => {const ReactDOM = require ( 'react-dom' )const importedComponent = require ( 'react-imported-component' )importedComponent.rehydrateMarks.mockImplementation( () => Promise .resolve())// mock module.hotconst module = {}// mock rehydrate functionconst hydrate = jest.fn()// mock optionsconst options = {isProduction : true ,module ,document ,hydrate}start(options)expect(ReactDOM.render).not.toBeCalled()expect(hydrate).toBeCalled()})})describe( '#hydrate' , () => {it( 'uses ReactDOM to hydrate given element with an app' , () => {const ReactDOM = require ( 'react-dom' )const element = document .getElementById( 'app' )const app = ( < div > </ div > )const doHydrate = hydrate(app, element)expect( typeof doHydrate).toBe( 'function' )doHydrate()expect(ReactDOM.hydrate).toBeCalledWith(app, element)})})
})

现在,当我们运行测试时,除了应生成的文件app/imported.js之外,我们应该对app文件夹具有100%的覆盖率,并且进行测试没有意义,因为它在将来的版本中可能会有所不同。

让我们更新jest配置,以从coverage统计信息中忽略它,并检查结果。

jest.config添加:

"coveragePathIgnorePatterns" : ["<rootDir>/app/imported.js" ,"/node_modules/"
]

现在,当我们运行npm run test我们得到以下结果。

GitHub链接test:client.js tests·patrickleet / streaming-ssr-react-styled-components @ c5fcfe9

我想指出的是,在开发测试时,我通常使用“监视”模式来执行此操作,因此在更改测试时,它们会自动重新运行。

完成应用程序测试后,让我们继续进行服务器。

服务器测试

在上一篇文章中,我为一个应用程序文件和一个服务器文件编写了测试,因此我们已经对server/index.js 进行了测试 。 现在我们需要测试server/lib剩余的三个文件。

让我们从server/lib/client.js

import fs from 'fs'
import path from 'path'
import cheerio from 'cheerio'
export const htmlPath = path.join(process.cwd(), 'dist' , 'client' , 'index.html' )
export const rawHTML = fs.readFileSync(htmlPath).toString()
export const parseRawHTMLForData = ( template, selector = '#js-entrypoint' ) => {const $template = cheerio.load(template)let src = $template(selector).attr( 'src' )return {src}
}
const clientData = parseRawHTMLForData(rawHTML)
const appString = '<div id="app">'
const splitter = '###SPLIT###'
const [startingRawHTMLFragment, endingRawHTMLFragment] = rawHTML.replace(appString, ` ${appString} ${splitter} ` ).split(splitter)
export const getHTMLFragments = ( { drainHydrateMarks } ) => {const startingHTMLFragment = ` ${startingRawHTMLFragment} ${drainHydrateMarks} `return [startingHTMLFragment, endingRawHTMLFragment]
}

首先,我注意到有一个很大的代码块,以前的废弃策略中甚至没有在项目中使用过。 从export const parseRawHTMLForDataconst clientData

我将从删除它开始。 代码越少,错误可以存在的位置就越少。 还有一些我从未使用过的导出可以对模块保持私有。

这是更新的文件:

import fs from 'fs'
import path from 'path'
const htmlPath = path.join(process.cwd(), 'dist' , 'client' , 'index.html' )
const rawHTML = fs.readFileSync(htmlPath).toString()
const appString = '<div id="app">'
const splitter = '###SPLIT###'
const [startingRawHTMLFragment, endingRawHTMLFragment] = rawHTML.replace(appString, ` ${appString} ${splitter} ` ).split(splitter)
export const getHTMLFragments = ( { drainHydrateMarks } ) => {const startingHTMLFragment = ` ${startingRawHTMLFragment} ${drainHydrateMarks} `return [startingHTMLFragment, endingRawHTMLFragment]
}

看起来可能需要为此进行一项测试。 但是,该计划中有一个小问题:该文件取决于之前运行的构建,因为它读取生成的构建。

从技术上讲,这是有道理的,因为您永远都不会尝试在没有内置应用程序呈现的情况下在服务器上呈现应用程序。

考虑到该约束,我认为这是可以的,并且由于我们只能确保在测试之前就建立了管道调用,因此不值得进行重构。 如果我们想要真正的纯单元隔离,我们可能会考虑重构一些,因为从技术上讲整个应用程序都是SSR的依赖项,因此可以对其进行模拟。 另一方面,使用实际构建可能反而更有用。 在编写测试的整个过程中,您经常会遇到这样的折衷。

话虽如此,这是测试以获得该模块的完整介绍:

import { getHTMLFragments } from 'server/lib/client.js'
describe( 'client' , () => {it( 'exists' , () => {const drainHydrateMarks = '<!-- mock hydrate marks -->'const [start, end] = getHTMLFragments({ drainHydrateMarks })expect(start).toContain( '<head>' )expect(start).toContain(drainHydrateMarks)expect(end).toContain( 'script id="js-entrypoint"' )})
})

并提交: 修复:删除未使用的代码来分析模板 , 测试:服务器/库/客户端测试 。

接下来, server/lib/server.js很小,所以让我们把它淘汰掉。 以下是其代码以刷新您的记忆,或者如果您现在才加入我们:

import express from 'express'
export const server = express()
export const serveStatic = express.static

和测试:

import express from 'express'
import { server, serveStatic } from 'server/lib/server.js'
describe( 'server/lib/server' , () => {it( 'should provide server APIs to use' , () => {expect(server).toBeDefined()expect(server.use).toBeDefined()expect(server.get).toBeDefined()expect(server.listen).toBeDefined()expect(serveStatic).toEqual(express.static)})
})

似乎我们基本上只是在放任所有快递责任,而我们希望快递提供这份合同,我们只需确保它能做到,而超出此范围则没有任何意义。

最后,我们只有一个文件要测试: server/lib/ssr.js

这是我们的ssr模块:

import React from 'react'
import { renderToNodeStream } from 'react-dom/server'
import { HelmetProvider } from 'react-helmet-async'
import { StaticRouter } from 'react-router-dom'
import { ServerStyleSheet } from 'styled-components'
import { printDrainHydrateMarks } from 'react-imported-component'
import log from 'llog'
import through from 'through'
import App from '../../app/App'
import { getHTMLFragments } from './client'
// import { getDataFromTree } from 'react-apollo';
export default (req, res) => {const context = {}const helmetContext = {}const app = (< HelmetProvider context = {helmetContext} > <StaticRouter location={req.originalUrl} context={context}><App /></StaticRouter> </ HelmetProvider >)try {// If you were using Apollo, you could fetch data with this// await getDataFromTree(app);const sheet = new ServerStyleSheet()const stream = sheet.interleaveWithNodeStream(renderToNodeStream(sheet.collectStyles(app)))if (context.url) {res.redirect( 301 , context.url)} else {const [startingHTMLFragment, endingHTMLFragment] = getHTMLFragments({drainHydrateMarks : printDrainHydrateMarks()})res.status( 200 )res.write(startingHTMLFragment)stream.pipe(through(function write ( data )  {this .queue(data)},function end ()  {this .queue(endingHTMLFragment)this .queue( null )})).pipe(res)}} catch (e) {log.error(e)res.status( 500 )res.end()}
}

它有点长,并且有一些执行路径。 我确实想做一些小的重构,这将使隔离更加容易,例如提取逻辑以将应用程序生成为单独的函数,并使用部分应用程序能够注入应用程序流渲染器,以便我们可以轻松地进行模拟一些重定向。

另外,写入和结束也有些困难,因此我们也可以使用部分应用程序将它们拉高。

这是更新的版本:

import React from 'react'
import { renderToNodeStream } from 'react-dom/server'
import { HelmetProvider } from 'react-helmet-async'
import { StaticRouter } from 'react-router-dom'
import { ServerStyleSheet } from 'styled-components'
import { printDrainHydrateMarks } from 'react-imported-component'
import log from 'llog'
import through from 'through'
import App from '../../app/App'
import { getHTMLFragments } from './client'
// import { getDataFromTree } from 'react-apollo';
const getApplicationStream = ( originalUrl, context ) => {const helmetContext = {}const app = (< HelmetProvider context = {helmetContext} > <StaticRouter location={originalUrl} context={context}><App /></StaticRouter> </ HelmetProvider >)const sheet = new ServerStyleSheet()return sheet.interleaveWithNodeStream(renderToNodeStream(sheet.collectStyles(app)))
}
export function write ( data )  {this .queue(data)
}
// partial application with ES6 is quite succinct
// it just means a function which returns another function
// which has access to values from a closure
export const end = endingHTMLFragment =>function end ()  {this .queue(endingHTMLFragment)this .queue( null )}
export const ssr = getApplicationStream => (req, res) => {try {// If you were using Apollo, you could fetch data with this// await getDataFromTree(app);const context = {}const stream = getApplicationStream(req.originalUrl, context)if (context.url) {return res.redirect( 301 , context.url)}const [startingHTMLFragment, endingHTMLFragment] = getHTMLFragments({drainHydrateMarks : printDrainHydrateMarks()})res.status( 200 )res.write(startingHTMLFragment)stream.pipe(through(write, end(endingHTMLFragment))).pipe(res)} catch (e) {log.error(e)res.status( 500 )res.end()}
}
const defaultSSR = ssr(getApplicationStream)
export default defaultSSR

以下是查看Github中差异的链接: chore:重构ssr以使其分解/使其更易于阅读 ,以及chore:重构ssr more 。

现在让我们编写一些测试。 我们需要为此文件专门为节点设置jest-environment,否则styled-components部分将不起作用。

/*** @jest-environment node*/
import defaultSSR, { ssr, write, end } from 'server/lib/ssr.js'
jest.mock( 'llog' )
const mockReq = {originalUrl : '/'
}
const mockRes = {redirect : jest.fn(),status : jest.fn(),end : jest.fn(),write : jest.fn(),on : jest.fn(),removeListener : jest.fn(),emit : jest.fn()
}
describe( 'server/lib/ssr.js' , () => {describe( 'ssr' , () => {it( 'redirects when context.url is set' , () => {const req = Object .assign({}, mockReq)const res = Object .assign({}, mockRes)const getApplicationStream = jest.fn( ( originalUrl, context ) => {context.url = '/redirect'})const doSSR = ssr(getApplicationStream)expect( typeof doSSR).toBe( 'function' )doSSR(req, res)expect(res.redirect).toBeCalledWith( 301 , '/redirect' )})it( 'catches error and logs before returning 500' , () => {const log = require ( 'llog' )const req = Object .assign({}, mockReq)const res = Object .assign({}, mockRes)const getApplicationStream = jest.fn( ( originalUrl, context ) => {throw new Error ( 'test' )})const doSSR = ssr(getApplicationStream)expect( typeof doSSR).toBe( 'function' )doSSR(req, res)expect(log.error).toBeCalledWith( Error ( 'test' ))expect(res.status).toBeCalledWith( 500 )expect(res.end).toBeCalled()})})describe( 'defaultSSR' , () => {it( 'renders app with default SSR' , () => {const req = Object .assign({}, mockReq)const res = Object .assign({}, mockRes)defaultSSR(req, res)expect(res.status).toBeCalledWith( 200 )expect(res.write.mock.calls[ 0 ][ 0 ]).toContain( '<!DOCTYPE html>' )expect(res.write.mock.calls[ 0 ][ 0 ]).toContain('window.___REACT_DEFERRED_COMPONENT_MARKS')})})describe( '#write' , () => {it( 'write queues data' , () => {const context = {queue : jest.fn()}const buffer = new Buffer.from( 'hello' )write.call(context, buffer)expect(context.queue).toBeCalledWith(buffer)})})describe( '#end' , () => {it( 'end queues endingFragment and then null to end stream' , () => {const context = {queue : jest.fn()}const endingFragment = '</html>'const doEnd = end(endingFragment)doEnd.call(context)expect(context.queue).toBeCalledWith(endingFragment)expect(context.queue).toBeCalledWith( null )})})
})

由于此文件比其他文件复杂一些,因此需要进行更多测试才能打到所有分支。 为了清楚起见,每个函数都包装在其自己的describe块中。

这是对Github的提交: test:ssr单元测试 。

现在,当我们运行测试时,我们具有100%的覆盖率!

最后,在整理内容之前,我将对jest.config进行一些小的更改以强制执行100%的覆盖率。 与第一次接触相比,保持覆盖范围要容易得多。 我们测试的许多模块几乎不会改变。

"coverageThreshold" : {"global" : {"branches" : 100 ,"functions" : 100 ,"lines" : 100 ,"statements" : 100}},

并做了! 这是Github上的提交: 杂项:要求100%覆盖率 。

结论

我在本文中的目的是演示能够重构代码或使用模拟和依赖注入来隔离单元的技术,以使难以测试的代码难以实现,并讨论达到100%覆盖率的某些优点。 此外,从起点开始使用TDD会容易得多。

我坚信,如果100%的覆盖率很难达到,那是因为代码需要重构。

在许多情况下,端到端测试对于某些方面来说将是更好的测试。 基于此的Cypress.io套件可以加载应用程序并单击,这将大大提高我们的信心。

我相信,在覆盖率达到100%的代码库中,可以极大地提高您对每个发行版的信心,从而提高做出和检测重大更改的速度,从而发挥了很大作用。

与往常一样,如果您发现这很有用,请鼓掌,关注我, 在GitHub项目上留下星星,和/或在社交网络上分享!

在接下来的部分中,即将到来的是,我们将添加可用于生产的Dockerfile,并探索如何仅使用另一个Dockerfile,或者将我们的应用程序打包为Nginx服务的静态站点,以及这两种方法之间的权衡。

最好,
帕特里克·李·斯科特

查看本系列的其他文章! 这是第4部分。