您是否曾经想为您的代码编写单元测试,但是您发现这样做很难? 通常这是由于不考虑测试而编写代码的结果。 解决此问题的一种简单方法是利用测试驱动的开发 ,这种开发过程是您在应用程序代码之前编写测试。
但是,即使您不喜欢测试驱动的开发,也可以通过采用一种简单的技术( 依赖项注入)使代码更易于测试,这将在本文中进行讨论。
什么是依赖注入?
依赖注入是一种非常简单但功能非常强大的技术。 简而言之,该函数不是将其依赖项硬编码到其中的功能,而是允许使用该函数的开发人员通过参数将其所需的任何依赖项传递给它。
为了帮助巩固这一概念,让我们一起看一个示例。
解析Cookie字符串
![](https://i-blog.csdnimg.cn/blog_migrate/d75de454a562bddbe19e0ad15782e8d2.png)
假设您要编写一个JavaScript函数,该函数可以从document.cookie
字符串中解析出各个Cookie键/值对。
例如,假设您要检查是否存在一个名为enable_cool_feature
的cookie,并且其值为true
,那么您想为该用户浏览站点启用一些很酷的功能。
不幸的是, document.cookie
字符串在JavaScript中使用绝对是糟糕的。 如果我们仅可以使用诸如document.cookie.enable_cool_feature
类的属性值来查找属性值,那很好,但是,我们不能。
因此,我们将诉诸编写自己的cookie解析函数,该函数将为一些潜在的复杂基础代码提供简单的外观。
(据记录,这里有多个JavaScript库和程序包已经做到了这一点,因此除非您愿意,否则不必在自己的应用程序中重新编写此函数。)
首先,我们可能希望定义一个简单的函数,如下所示:
function getCookie ( cookieName ) { /* body here */ }
此函数将允许我们通过像这样调用它来查找特定cookie的值:
getCookie( 'enable_cool_feature' )
样品溶液
AbsolutVision 在 Unsplash 上 拍摄的 照片
谷歌搜索“如何在JavaScript中解析cookie字符串”揭示了来自各种开发人员的许多不同解决方案。 对于本文,我们将看一下W3Schools提供的解决方案。 看起来像这样:
export function getCookie ( cookieName ) {
var name = cookieName + '='
var decodedCookie = decodeURIComponent ( document .cookie)
var ca = decodedCookie.split( ';' )
for ( var i = 0 ; i < ca.length; i++) {
var c = ca[i]
while (c.charAt( 0 ) == ' ' ) {
c = c.substring( 1 )
}
if (c.indexOf(name) == 0 ) {
return c.substring(name.length, c.length)
}
}
return ''
}
对样品溶液的批评
![](https://i-blog.csdnimg.cn/blog_migrate/e859377f102c8045d308ddcc23dc1f14.png)
现在,这怎么了? 我们不会批评代码本身的主体,而是看一下这一行代码:
var decodedCookie = decodeURIComponent ( document .cookie)
函数getCookie
依赖于document
对象和cookie
属性! 乍一看似乎没什么大不了的,但是确实有一些缺点。
首先,如果由于某种原因我们的代码无法访问document
对象怎么办? 例如,在Node环境中, document
是undefined
。 让我们看一些示例测试代码来说明这一点。
让我们使用Jest作为我们的测试框架,然后编写两个测试:
import { getCookie } from './get-cookie-bad'
describe( 'getCookie - Bad' , () => {
it( 'can correctly parse a cookie value for an existing cookie' , () => {
document .cookie = 'key2=value2'
expect(getCookie( 'key2' )).toEqual( 'value2' )
})
it( 'can correctly parse a cookie value for an nonexistent cookie' , () => {
expect(getCookie( 'bad_key' )).toEqual( '' )
})
})
现在,让我们运行测试以查看输出。
ReferenceError: document is not defined
不好了! 在节点环境中,未定义document
。 幸运的是,我们可以在jest.config.js
文件中更改Jest配置,以指定我们的环境应为jsdom
,这将创建一个DOM供我们在测试中使用。
module .exports = {
testEnvironment : 'jsdom'
}
现在,如果我们再次运行测试,它们将通过。 但是,我们仍然有一些问题。 我们正在全局修改document.cookie
字符串,这意味着我们的测试现在相互依赖。 如果我们的测试以不同的顺序运行,这可能会导致一些奇怪的测试案例。
例如,如果我们在第二个测试中编写console.log(document.cookie)
,它将仍然输出key2=value2
。 不好了! 那不是我们想要的。 我们的第一个测试正在影响我们的第二个测试。 在这种情况下,第二个测试仍然可以通过,但是当您的测试彼此之间没有隔离时,很有可能会陷入一些令人困惑的情况。
为了解决这个问题,我们可以在第一个测试的expect
语句之后做一些清理工作:
it( 'can correctly parse a cookie value for an existing cookie' , () => {
document .cookie = 'key2=value2'
expect(getCookie( 'key2' )).toEqual( 'value2' )
document .cookie = 'key2=; expires = Thu, 01 Jan 1970 00:00:00 GMT'
})
(通常,我建议您在afterEach
方法中进行一些清理,该方法在每次测试后在其中运行代码。但是,不幸的是,删除cookie并不像只说document.cookie = ''
那样简单。)
如果您想解析document.cookie
属性中当前未设置的cookie字符串,则W3Schools解决方案的第二个问题就会出现。 你会怎么做? 在这种情况下,您不能!
有一个更好的方法
![](https://i-blog.csdnimg.cn/blog_migrate/44637975cd4f3f7823c4dfeb571df4ac.jpeg)
现在,我们已经探讨了一种可能的解决方案及其两个问题,下面让我们看一下编写此方法的更好方法。 我们将使用依赖注入!
我们的功能签名看起来与我们最初的解决方案有些不同。 这次,它将接受两个参数:
function getCookie ( cookieString, cookieName ) { /* body here */ }
所以我们可以这样称呼它:
getCookie( < someCookieStringHere > 'enable_cool_feature')
一个示例实现可能如下所示:
export function getCookie ( cookieString, cookieName ) {
var name = cookieName + '='
var decodedCookie = decodeURIComponent (cookieString)
var ca = decodedCookie.split( ';' )
for ( var i = 0 ; i < ca.length; i++) {
var c = ca[i]
while (c.charAt( 0 ) == ' ' ) {
c = c.substring( 1 )
}
if (c.indexOf(name) == 0 ) {
return c.substring(name.length, c.length)
}
}
return ''
}
请注意,此函数与原始函数之间的唯一区别是该函数现在接受两个参数,并且在对第3行的cookie进行解码时将其用于cookieString
。
现在让我们为此功能编写两个测试。 这两个测试将测试与原始两个测试相同的内容:
import { getCookie } from './get-cookie-good'
describe( 'getCookie - Good' , () => {
it( 'can correctly parse a cookie value for an existing cookie' , () => {
const cookieString = 'key1=value1;key2=value2;key3=value3'
const cookieName = 'key2'
expect(getCookie(cookieString, cookieName)).toEqual( 'value2' )
})
it( 'can correctly parse a cookie value for an nonexistent cookie' , () => {
const cookieString = 'key1=value1;key2=value2;key3=value3'
const cookieName = 'bad_key'
expect(getCookie(cookieString, cookieName)).toEqual( '' )
})
})
注意我们如何才能完全控制我们的方法现在使用的cookie字符串。
我们不必依赖于环境,我们也不会遇到任何测试障碍,也不必假设我们总是直接从document.cookie
解析cookie。
好多了!
结论
而已! 依赖项注入非常容易实现,并且通过使测试易于编写和易于模拟依赖关系,将极大地改善您的测试体验。 (更不用说它有助于解耦代码,但这是另一天的话题。)
谢谢阅读!
(最初在此处发布)
From: https://hackernoon.com/dependency-injection-to-make-your-code-testable-a-how-to-guide-kj883211