(由Adobe Stock Photo许可)
互联网上现在有很多建议说100%的覆盖率不是一个值得的目标。
我非常不同意。
通常,难以测试的代码是需要重构的信号。
我知道了。 几年前,我很讨厌测试。 我以为这只会让我移动得更慢。
当我开始编码时,这并不是人们经常做的事情。 如果是这样,通常是由一个单独的质量检查小组负责。 几年前,尽管它成为一个真正的热门话题。 面试开始期望候选人知道如何编写测试,并且越来越多的组织将其作为质量计划从上至下进行推广。
我一直努力在比赛中处于领先地位,因此我决定走进面试,并说“测试并不是我真正的强项”不再是一个好看的样子,所以我决定我将获得所有比赛的100%覆盖率从那时起我的测试。
当时,我不确定自己会从中获得什么好处,或者是否真的有任何好处。
现在, 老实说 ,我不会回去。 当某件事破坏了覆盖率为100%的代码库时,您的测试很可能会告诉您确切的位置和方式。
这并不是说单元测试就是您所需要的。 不是。 但是在我看来,未经测试的代码也不是一个好选择。
回到我身边,回到我也不相信测试覆盖范围的好处的时代。
第1部分:学习Lingo
当时,交易工具是摩卡咖啡 , 西农 咖啡和柴 奶酪的组合。 Mocha是测试运行程序,sinon提供了创建“模拟”和“间谍”的功能,而chai是一个断言库,因此您可以以人类语言友好的方式键入断言。
(这是间谍吗?)
我基本上不知道这意味着什么。 在我变得有效之前,首先要做的就是学习语言。
因此,第一件事是第一位- 地狱是间谍还是嘲笑?
尽管首先想到的是詹姆斯·邦德或伊桑·亨特。 尽管这不是一个可怕的隐喻,但这绝对不是我们在这里谈论的内容。
在阅读了一些文档之后,我最终了解到,间谍程序是一种功能,已通过测试框架进行了修改,以提供有关其用法的元信息。 间谍。 Kinda喜欢人们如何利用Apple最近的FaceTime Bug监视您。 有点像詹姆斯·邦德。
模拟与间谍相似,但经过了更多修改。 除了提供并跟踪如何使用特定功能外,它还将其行为更改为可预测的。
我还了解到有几种测试类型。 不限于三种最常见的:单元测试,集成测试和E2E测试。
当我们进行“单元测试”时,这意味着我们需要能够将代码分解为各个单元。 该特定单元之外的任何内容都是要被嘲弄的候选对象,例如其他功能或整个模块。 开玩笑是我选择单元测试的工具。 单元测试是衡量覆盖率的唯一测试类型。
当我们进行集成测试时,我们正在测试我们的软件与其他软件的集成,例如,通过Kafka传递一条消息的测试,该消息应该由我们的服务接收,然后可以在数据库中找到结果。 在创建集成测试时,我通常也喜欢Jest。
端到端测试有点像使用您的应用程序的机器人。 您可以对其进行编程,以将其加载到浏览器中的网站,单击内容,并确保一切都能从用户角度正常运行。 赛普拉斯是我在该领域最喜欢的工具,但在我学习时还不存在。 Selenium是当今的佼佼者,老实说,这是一个足够大的领域,我很乐意让QA自动化工程师来处理这一部分。
现在掌握了新知识就成了困难的部分:将其付诸实践。
我花了几个月的时间来确保我编写的每段代码都具有测试覆盖率。 我承认,起初很难。 我在StackOverflow上花费了大量时间来查找模拟和间谍示例。 到最后,我发现我对代码的信心大大提高了。
另一个好处是,如果发生故障,我的测试通常会告诉我确切的位置。 当其他工程师对我所做的代码进行更改时,我可以更快地对其进行检查。 当重要的API发生更改时,会通过失败的测试向人们发出警报,并迅速对其进行更新或重新考虑他们的更改。
不仅如此,我开始编写更好的代码。 我了解到,通常情况下,如果难以测试或难以完全覆盖某些内容,通常意味着我编写的代码不太好,并且可以对其进行重构,从而获得更加可维护和灵活的API。 为此,尝试达到100%的覆盖率鼓励了我将匿名函数提取到命名函数中,并了解许多重构中的部分应用程序和依赖项注入。
在完成集成测试之后,我甚至放弃了GitFlow来进行基于主干的开发。 几年前,我以为要成为大师是一件疯狂的事情,现在我每天要由近15名工程师组成的团队来做。
第2部分:以身作则
大约在我对新的测试堆栈充满信心的时候,另一种工具被推向市场,许多人声称它使单元测试变得更加简单:Jest。
Jest是Facebook率先推出的自动化测试框架。
Jest的工作非常出色,将我以前使用的库浓缩到一个统一的框架中,该框架是一个测试运行器,并且提供了一组用于模拟,监视和断言的API。 除了为您的所有单元测试需求提供单个库之外,Jest在简化某些概念和模式以及强大而简单的模拟方面也做得很出色。
因为我认为Jest更易于使用和理解,所以我将继续以Jest为例。
如果您只是和我一起写这篇文章,那很好-到目前为止,您所读的内容都将独立存在。 但是,我一直在记录使用Parcel和Streaming SSR来构建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在测试方面提供了相同的好处。
让我们从淘汰其余的简单组件开始。 我们基本上只需要渲染它们,并可能检查是否渲染了一些重要的信息。
我通常将代码内联在文章中,但是这些测试中并没有什么新内容,因此我决定链接到实际的提交,只显示一个完整的示例:
让我们看一下About页面:
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 marks
rehydrateMarks().then( () => {
ReactDOM.hydrate(app, element)
})
} else {
ReactDOM.render(app, element)
}
// Enable Hot Module Reloading
if ( module .hot) {
module .hot.accept()
}
我注意到的第一件事是依赖全局变量document
, process
和module
。 第二件事是什么也没有导出,因此可能很难用不同的输入多次运行。
我们可以通过一些重构来解决这个问题:
- 将所有逻辑包装到我们可以导出的函数中。 此函数将接受具有所有依赖项的options对象。 这称为依赖注入 。 如果选择的话,这将使我们能够轻松地传递一堆东西的模拟版本。
- 重新水化后,我们在生产模式下有一个匿名函数,应将其提取为命名函数。
我们还将要模拟一些外部模块: react-dom
, react-imported-component
和app/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-rendering
if (isProduction) {
// rehydrate the bundle marks from imported-components,
// then rehydrate the react app
rehydrateMarks().then(hydrate(app, element))
} else {
ReactDOM.render(app, element)
}
// Enable Hot Module Reloading
if ( 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 test
afterEach( () => {
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 called
const ReactDOM = require ( 'react-dom' )
// mock module.hot
const module = {
hot : {
accept : jest.fn()
}
}
// mock options
const 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.hot
const module = {}
// mock rehydrate function
const hydrate = jest.fn()
// mock options
const 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配置,以从覆盖率统计信息中忽略它,并检查结果。
在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 parseRawHTMLForData
到const 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部分。
- 第1部分:移至Next.js和Webpack🤯
- 第2部分:使用Docker开发Node.js的更好方法
- 第3部分:增强Node.js的代码质量
- 第5部分:两层(Docker多阶段构建)层的故事
- 第6部分:引入机器人,让他们维护我们的代码
From: https://hackernoon.com/the-100-code-coverage-myth-900b83d20d3d