博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
react测试组件_测试驱动的开发,功能和React组件
阅读量:2522 次
发布时间:2019-05-11

本文共 19069 字,大约阅读时间需要 63 分钟。

react测试组件

This article is part of my studies on how to build sustainable and consistent software. In this post, we will talk about the thinking behind the testing driven development and how to apply this knowledge to simple functions, web accessibility, and React components, mostly with Jest and React Testing Library.

本文是我关于如何构建可持续且一致的软件的研究的一部分。 在本文中,我们将讨论测试驱动开发背后的思想,以及如何将这些知识应用于简单功能,Web可访问性和React组件(主要是通过Jest和React Testing库)。

Automated tests are a big part of software development. It gives us, developers, confidence to ship code to be there, but we increase the confidence that the software will be up and running and working appropriately.

自动化测试是软件开发的重要组成部分。 它给我们开发人员提供了将代码发布到那里的信心,但是,我们增加了该软件可以正常运行并运行的信心。

I began my software career in the Ruby community writing tests from the first day I learned the language. The Ruby (and Rails) community was always strong in the testing automation area. It helped shape my mindset on how to write good software.

从学习语言的第一天起,我就在Ruby社区开始了我的软件职业生涯,编写测试。 Ruby(和Rails)社区在测试自动化领域一直很强大。 它帮助我塑造了如何编写优质软件的心态。

So using Ruby and Rails, I did a lot of backend stuff like background jobs, data structure modeling, API building, and so on. In this scope, the user is always one: the developer user. If building an API, the user would be the developer that's consuming the API. If building the models, the user would be the developer that will use this model.

因此,使用Ruby和Rails,我做了很多后端工作,例如后台作业,数据结构建模,API构建等等。 在此范围内,用户始终是一个:开发人员用户。 如果构建API,则用户将是使用该API的开发人员。 如果构建模型,则用户将是将使用此模型的开发人员。

Now doing a lof of frontend stuff too, after 1 intense year of building PWAs using mostly React and Redux, at first some thoughts came to my mind:

经过一年的紧张工作(主要使用React和Redux构建PWA)之后,现在也正在做一些前端工作,首先想到的是:

  • TDD is impossible when building UI stuff. How do I know if it is a div or span?

    在构建UI内容​​时,TDD是不可能的。 我怎么知道它是div还是span?
  • Testing can be "complex". Should I shallow or should I mount? Test everything? Ensure every div should be the right place?

    测试可以是“复杂的”。 我应该变浅还是应该安装? 测试一切? 确保每个div应该在正确的位置?

So I started re-thinking about these testing practices and how to make it productive.

因此,我开始重新考虑这些测试实践以及如何使其富有成效。

TDD is possible. If I'm wondering if I should expect a div or a span, I'm probably testing the wrong thing. Remember: tests should give us the confidence to ship, not necessarily to cover every bit or implementation details. We will dive into this topic later!

TDD是可能的。 如果我想知道是否应该使用div或span,我可能正在测试错误的东西。 请记住:测试应该使我们充满信心,而不一定要覆盖每一个细节或实施细节。 我们稍后将深入探讨该主题!

I want to build tests that:

我想建立以下测试:

  • Ensure the software works appropriately

    确保软件正常运行
  • Give the confidence to ship code to production

    放心将代码交付生产
  • Make us think about software design

    让我们考虑软件设计

And tests that make software:

以及制作软件的测试:

  • Easy to maintain

    易于维护
  • Easy to refactor

    易于重构

测试驱动开发 (Testing Driven Development)

TDD shouldn't be complex. It is just a process of 3 steps:

TDD应该不复杂。 这只是一个3个步骤的过程:

  • Make a test

    做个测试
  • Make it run

    使它运行
  • Make it right

    改正它

We start writing a simple test to cover how we expect the software works. Then we make the first implementation of the code (class, function, script, etc). Now the software is behaving. It works as expected. Time to make it right. Time to make it better.

我们开始编写一个简单的测试,以涵盖我们期望软件如何工作。 然后,我们对代码(类,函数,脚本等)进行第一个实现。 现在该软件运行正常。 它按预期工作。 是时候做对了。 是时候让它变得更好。

The goal is a clean code that works. We solve the "that works" problem first and then make the code clean.

目标是有效的干净代码。 我们首先解决“有效”的问题,然后使代码干净。

It is pretty simple. And it should be. I didn't say it is easy. But it is simple, straightforward, just 3 steps. Every time you exercise this process of writing tests first, code after, and then refactoring, you feel more confident.

这很简单。 它应该是。 我没有说这很容易。 但这很简单,直接,只需三个步骤。 每次执行此过程时,首先要编写测试,然后编写代码,然后进行重构,您会感到更加自信。

One good technique when writing your tests first is to think about use cases and simulate how it should be used (as a function, component, or used by a real user).

首先编写测试的一种好方法是考虑用例并模拟应如何使用它(作为功能,组件或由实际用户使用)。

功能 (Functions)

Let's apply this TDD thing into simple functions.

让我们将此TDD事物应用到简单函数中。

Some time ago I was implementing a draft feature for a real estate registration flow. Part of the feature was to show a modal if the user had a not finished real estate. The function we will implement is the one that answers if the user has at least one real estate draft.

不久前,我正在为房地产注册流程实施功能草案。 该功能的一部分是在用户未完成房地产交易时显示模式。 如果用户至少拥有一份房地产草案,我们将实现的功能就是回答这一功能。

So first step: writing the test! Let's think of the use cases of this function. It always responds a boolean: true or false.

所以第一步:编写测试! 让我们考虑一下此函数的用例。 它总是响应一个布尔值:true或false。

  • Has no unsaved real estate draft: false

    没有未保存的房地产汇票: false

  • Has at least one unsaved real estate draft: true

    至少有一份未保存的房地产汇票: true

Let's write the tests that represent this behavior:

让我们编写代表这种行为的测试:

describe('hasRealEstateDraft', () => {  describe('with real estate drafts', () => {    it('returns true', () => {      const realEstateDrafts = [        {          address: 'São Paulo',          status: 'UNSAVED'        }      ];      expect(hasRealEstateDraft(realEstateDrafts)).toBeTruthy();    });  });  describe('with not drafts', () => {    it('returns false', () => {      expect(hasRealEstateDraft([])).toBeFalsy();    });  });});

We wrote the tests. But when running it, it shows go red: 2 broken tests because we do not have the function implemented yet.

我们编写了测试。 但是运行它时,它会变成红色:2个失败的测试,因为我们尚未实现该功能。

Second step: make it run! In this case, it is pretty simple. We need to receive this array object and return if it has or hasn't at least one real estate draft.

第二步:使其运行! 在这种情况下,这非常简单。 我们需要接收此数组对象,并返回是否包含至少一个房地产草案。

const hasRealEstateDraft = (realEstateDrafts) => realEstateDrafts.length > 0;

Great! Simple function. Simple tests. We could go to step 3: make it right! But in this case, our function is really simple and we've already got it right.

大! 功能简单。 简单的测试。 我们可以进行第3步:正确设置! 但是在这种情况下,我们的功能确实很简单,并且我们已经做好了。

But now we need the function to get the real estate drafts and pass it to the hasRealEstateDraft.

但是现在我们需要函数来获取房地产草稿并将其传递给hasRealEstateDraft

Which use case we can think of?

我们能想到哪个用例?

  • An empty list of real estates

    空的房地产清单
  • Only saved real estates

    仅保存的房地产
  • Only unsaved real estates

    仅未保存的房地产
  • Mixed: save and unsaved real estates

    混合:保存和未保存的房地产

Let's write the tests to represent it:

让我们编写测试来表示它:

describe('getRealEstateDrafts', () => {  describe('with an empty list', () => {    it('returns an empty list', () => {      const realEstates = [];      expect(getRealEstateDrafts(realEstates)).toMatchObject([]);    });  });  describe('with only unsaved real estates', () => {    it('returns the drafts', () => {      const realEstates = [        {          address: 'São Paulo',          status: 'UNSAVED'        },        {          address: 'Tokyo',          status: 'UNSAVED'        }      ];      expect(getRealEstateDrafts(realEstates)).toMatchObject(realEstates);    });  });  describe('with only saved real estates', () => {    it('returns an empty list', () => {      const realEstates = [        {          address: 'São Paulo',          status: 'SAVED'        },        {          address: 'Tokyo',          status: 'SAVED'        }      ];      expect(getRealEstateDrafts(realEstates)).toMatchObject([]);    });  });  describe('with saved and unsaved real estates', () => {    it('returns the drafts', () => {      const realEstates = [        {          address: 'São Paulo',          status: 'SAVED'        },        {          address: 'Tokyo',          status: 'UNSAVED'        }      ];      expect(getRealEstateDrafts(realEstates)).toMatchObject([{        address: 'Tokyo',        status: 'UNSAVED'      }]);    });  });});

Great! We run the tests. It doesn't work.. yet! Now implement the function.

大! 我们运行测试。 它不起作用..呢! 现在实现该功能。

const getRealEstatesDrafts = (realEstates) => {  const unsavedRealEstates = realEstates.filter((realEstate) => realEstate.status === 'UNSAVED');  return unsavedRealEstates;};

We simply filter by the real estate status and return it. Great, the tests are passing, the bar is green! And the software is behaving, but we can make it better: step 3!

我们仅按房地产状态进行过滤并返回。 太好了,测试通过了,酒吧是绿色的! 软件运行正常,但是我们可以做得更好:第3步!

What about extracting the anonymous function within the filter function and make the 'UNSAVED' be represented by an enum?

如何在filter函数中提取匿名函数并使'UNSAVED'由枚举表示呢?

const STATUS = {  UNSAVED: 'UNSAVED',  SAVED: 'SAVED',};const byUnsaved = (realEstate) => realEstate.status === STATUS.UNSAVED;const getRealEstatesDrafts = (realEstates) => realEstates.filter(byUnsaved);

The tests are still passing and we have a better solution.

测试仍在通过,我们有一个更好的解决方案。

One thing to have in mind here: I isolated the data source from the logic. What does it mean? We get the data from local storage (data source), but we test only the functions responsible to the logic to get drafts and see if it has at least one draft. The functions with the logic, we ensure that it works and it is clean code.

这里要记住的一件事:我将数据源与逻辑隔离。 这是什么意思? 我们从本地存储(数据源)获取数据,但是我们仅测试负责获取草稿的逻辑的功能,并查看其是否至少有一份草稿。 具有逻辑的功能,我们确保它可以正常工作,并且代码干净。

If we get the localStorage inside our functions, it becomes hard to test. So we separate the responsibility and make the tests easy to write. Pure functions are easier to maintain and simpler to write tests.

如果我们将localStorage放入函数中,则很难进行测试。 因此,我们将职责分开,并使测试易于编写。 纯函数更易于维护,编写测试也更简单。

React组件 (React Components)

Now let's talk about React components. Back to the introduction, we talked about writing tests that test implementation details. And now we will see how we can make it better, more sustainable, and have more confidence.

现在让我们谈谈React组件。 回到引言,我们讨论了编写测试实现细节的测试。 现在,我们将看到如何使它变得更好,更具可持续性并更有信心。

A couple of days ago I was planning to build the new onboarding information for the real estate owner. It is basically a bunch of pages with the same design, but it changes the icon, title, and description of the pages.

几天前,我正计划为房地产所有者建立新的入职信息。 它基本上是一堆具有相同设计的页面,但是它更改了页面的图标,标题和描述。

I wanted to build just one component: Content and pass the information needed to render the correct icon, title, and description. I would pass businessContext and step as props and it would render the correct content to the onboarding page.

我只想构建一个组件: Content并传递渲染正确的图标,标题和描述所需的信息。 我将把businessContextstep作为道具传递,它将正确的内容呈现给入职页面。

We don't want to know if we will render a div or paragraph tag. Our test needs to ensure that for a given business context and step, the correct content will be there. So I came with these use cases:

我们不想知道是否将渲染div或段落标签。 我们的测试需要确保对于给定的业务环境和步骤,正确的内容在那里。 因此,我想到了这些用例:

  • The first step of the rental business context

    租赁业务环境的第一步
  • Last step of the rental business context

    租赁业务环境的最后一步
  • The first step of the sales business context

    销售业务环境的第一步
  • Last step of the sales business context

    销售业务环境的最后一步

Let's see the tests:

让我们看一下测试:

describe('Content', () => {  describe('in the rental context', () => {    const defaultProps = {      businessContext: BUSINESS_CONTEXT.RENTAL    };    it('renders the title and description for the first step', () => {      const step = 0;      const { getByText } = render(
); expect(getByText('the first step title')).toBeInTheDocument(); expect(getByText('the first step description')).toBeInTheDocument(); }); it('renders the title and description for the forth step', () => { const step = 3; const { getByText } = render(
); expect(getByText('the last step title')).toBeInTheDocument(); expect(getByText('the last step description')).toBeInTheDocument(); }); }); describe('in the sales context', () => { const defaultProps = { businessContext: BUSINESS_CONTEXT.SALE }; it('renders the title and description for the first step', () => { const step = 0; const { getByText } = render(
); expect(getByText('the first step title')).toBeInTheDocument(); expect(getByText('the first step description')).toBeInTheDocument(); }); it('renders the title and description for the last step', () => { const step = 6; const { getByText } = render(
); expect(getByText('the last step title')).toBeInTheDocument(); expect(getByText('the last step description')).toBeInTheDocument(); }); });});

We have one describe block for each business context and an it block for each step. I also created an accessibility test to ensure the component we are building is accessible.

对于每个业务上下文,我们都有一个describe块,对于每个步骤都有一个it块。 我还创建了可访问性测试,以确保我们正在构建的组件可访问。

it('has not accessibility violations', async () => {  const props = {    businessContext: BUSINESS_CONTEXT.SALE,    step: 0,  };  const { container } = render(
); const results = await axe(container); expect(results).toHaveNoViolations();});

Now we need to make it run! Basically, the UI part of this component is just the icon, the title, and the description. Something like:

现在我们需要使其运行! 基本上,此组件的UI部分只是图标,标题和描述。 就像是:

{title}

{description}

We just need to build the logic to get all these correct data. As I have the businessContext and the step in this component, I wanted to just do something like

我们只需要构建逻辑来获取所有这些正确的数据。 因为有了businessContext和该组件中的step ,所以我想做一些类似的事情

content[businessContext][step]

And it gets the correct content. So I built a data structure to work that way.

并且它获取正确的内容。 因此,我建立了一个数据结构以这种方式工作。

const onboardingStepsContent = {  alugar: {    0: {      Icon: Home,      title: 'first step title',      description: 'first step description',    },    // ...  },  vender: {    0: {      Icon: Home,      title: 'first step title',      description: 'first step description',    },    // ...  },};

It's just an object with the first keys as the business context data and for each business context, it has keys that represent each step of the onboarding. And our component would be:

它只是一个具有第一个键作为业务上下文数据的对象,并且对于每个业务上下文,它都有代表入门过程中每个步骤的键。 我们的组件将是:

const Content = ({ businessContext, step }) => {  const onboardingStepsContent = {    alugar: {      0: {        Icon: Home,        title: 'first step title',        description: 'first step description',      },      // ...    },    vender: {      0: {        Icon: Home,        title: 'first step title',        description: 'first step description',      },      // ...    },  };  const { Icon, title, description } = onboardingStepsContent[businessContext][step];  return (    

{title}

{description}

);};

It works! Now let's make it better. I wanted to make the get content more resilient. What if it receives a step that doesn't exist for example? These are the use cases:

有用! 现在,让它变得更好。 我想使获取的内容更具弹性。 例如,如果收到不存在的步骤怎么办? 这些是用例:

  • The first step of the rental business context

    租赁业务环境的第一步
  • Last step of the rental business context

    租赁业务环境的最后一步
  • The first step of the sales business context

    销售业务环境的第一步
  • Last step of the sales business context

    销售业务环境的最后一步
  • Inexistent step of the rental business context

    租赁业务环境中不存在的步骤
  • Inexistent step of the sales business context

    销售业务环境中不存在的步骤

Let's see the tests:

让我们看一下测试:

describe('getOnboardingStepContent', () => {  describe('when it receives existent businessContext and step', () => {    it('returns the correct content for the step in "alugar" businessContext', () => {      const businessContext = 'alugar';      const step = 0;      expect(getOnboardingStepContent({ businessContext, step })).toMatchObject({        Icon: Home,        title: 'first step title',        description: 'first step description',      });    });    it('returns the correct content for the step in "vender" businessContext', () => {      const businessContext = 'vender';      const step = 5;      expect(getOnboardingStepContent({ businessContext, step })).toMatchObject({        Icon: ContractSign,        title: 'last step title',        description: 'last step description',      });    });  });  describe('when it receives inexistent step for a given businessContext', () => {    it('returns the first step of "alugar" businessContext', () => {      const businessContext = 'alugar';      const step = 7;      expect(getOnboardingStepContent({ businessContext, step })).toMatchObject({        Icon: Home,        title: 'first step title',        description: 'first step description',      });    });    it('returns the first step of "vender" businessContext', () => {      const businessContext = 'vender';      const step = 10;      expect(getOnboardingStepContent({ businessContext, step })).toMatchObject({        Icon: Home,        title: 'first step title',        description: 'first step description',      });    });  });});

Great! Now let's build our getOnboardingStepContent function to handle this logic.

大! 现在,让我们构建getOnboardingStepContent函数来处理此逻辑。

const getOnboardingStepContent = ({ businessContext, step }) => {  const content = onboardingStepsContent[businessContext][step];  return content    ? content    : onboardingStepsContent[businessContext][0];};

We try to get content. If we have it, just return it. If we don't have it, return the first step of the onboarding.

我们尝试获取内容。 如果有,请退回。 如果没有,请返回入职的第一步。

Neat! But we can improve it. What about using the || operator? No need to assign to a variable, no need to use a ternary.

整齐! 但是我们可以改善它。 怎样使用|| 操作员? 无需分配变量,无需使用三进制。

const getOnboardingStepContent = ({ businessContext, step }) =>  onboardingStepsContent[businessContext][step] ||  onboardingStepsContent[businessContext][0];

If it finds the content, just return it. If it didn't find, return the first step of the given business context.

如果找到内容,则将其返回。 如果找不到,请返回给定业务环境的第一步。

Now our component is only UI.

现在,我们的组件只是UI。

const Content = ({ businessContext, step }) => {  const {    Icon,    title,    description,  } = getOnboardingStepContent({ businessContext, step });  return (    

{title}

{description}

);};


最后的想法 (Final thoughts)

I like to think deeply about the tests I'm writing. And I think all developers should too. It does need to give us the confidence to ship more code and have a bigger impact on the market we are working on.

我喜欢对正在编写的测试进行深入思考。 而且我认为所有开发人员也应该这样做。 它确实需要使我们有信心发布更多代码,并对我们正在开发的市场产生更大的影响。

Like all code, when we write smelly and bad tests, it influences other developers to follow the "pattern". It gets worse in bigger companies. It scales badly. But we are always able to stop, reflect on the status quo, and take action to make it better.

像所有代码一样,当我们编写有臭味和不良的测试时,它会影响其他开发人员遵循“模式”。 在大公司中情况变得更糟。 它缩放严重。 但是我们总是能够停下来,反思现状,并采取行动使其变得更好。

I shared some resources I found interesting reading and learning. If you want to get a great introduction to TDD, I really recommend TDD by example, a book from Kent Beck.

我分享了一些有趣的阅读和学习资源。 如果您想对TDD进行很好的介绍,我真的以示例的方式推荐了TDD,这是一本肯特·贝克(Kent Beck)的书。

I will write more about tests, TDD, and React. And how we can make our software more consistent and feel safe when shipping code to production.

我将写更多关于测试,TDD和React的内容。 以及在将代码发布到生产环境时,如何使我们的软件更一致,更安全。

依存关系 (Dependencies)

  • : jest matchers for testing accessibility

    :用于测试可访问性的jest匹配器

  • : testing utilities to help test react

    :测试实用程序以帮助测试React

  • : jest matchers to test the state of the DOM

    :用于测试DOM状态的玩笑匹配者

资源资源 (Resources)

You can other articles like this .

您可以像这样的其他文章 。

翻译自:

react测试组件

转载地址:http://cfuzd.baihongyu.com/

你可能感兴趣的文章
Ubuntu菜鸟入门(五)—— 一些编程相关工具
查看>>
PHP开源搜索引擎
查看>>
12-FileZilla-响应:550 Permission denied
查看>>
ASP.NET MVC 3 扩展生成 HTML 的 Input 元素
查看>>
LeetCode 234. Palindrome Linked List
查看>>
编译HBase1.0.0-cdh5.4.2版本
查看>>
结构体指针
查看>>
迭代器
查看>>
Food HDU - 4292 (结点容量 拆点) Dinic
查看>>
Ubuntu安装Sun JDK及如何设置默认java JDK
查看>>
[经典算法] 排列组合-N元素集合的M元素子集
查看>>
Codeforces 279D The Minimum Number of Variables 状压dp
查看>>
打分排序系统漫谈2 - 点赞量?点赞率?! 置信区间!
查看>>
valgrind检测linux程序内存泄露
查看>>
Hadoop以及组件介绍
查看>>
1020 Tree Traversals (25)(25 point(s))
查看>>
第一次作业
查看>>
“==”运算符与equals()
查看>>
单工、半双工和全双工的定义
查看>>
Hdu【线段树】基础题.cpp
查看>>