256 lines
11 KiB
Plaintext
256 lines
11 KiB
Plaintext
include ../_util-fns
|
||
|
||
:marked
|
||
In this chapter we'll setup the environment for testing our sample application and write a few easy Jasmine tests of the app's simplest parts.
|
||
|
||
在这个章节,我们将为我们的样本应用程序设置测试环境,针对应用程序的最简单的部分,编写几个简单的Jasmine测试。
|
||
|
||
We'll learn:
|
||
我们会学到:
|
||
- to test one of our application files
|
||
- 测试我们应用程序的一个文件
|
||
- why we prefer our test files to be next to their corresponding source files
|
||
- 为什么我们比较喜欢把我们的测试文件放到它对应的源码旁边
|
||
- to run tests with an `npm` command
|
||
- 使用`npm`指令运行测试
|
||
- load the test file with SystemJS
|
||
- 使用SystemJs加载测试文件
|
||
|
||
.callout.is-helpful
|
||
header Prior Knowledge 预先需要的知识
|
||
:marked
|
||
The Unit Testing chapters build upon each other. We recommend reading them in order.
|
||
We're also assuming that you're already comfortable with basic Angular 2 concepts and the tools
|
||
we introduced in the [QuickStart](../quickstart.html) and
|
||
the [Tour of Heroes](../tutorial/) tutorial
|
||
such as <code>npm</code>, <code>gulp</code>, and <code>live-server</code>.
|
||
|
||
本单元测试章节是在其它章节的基础上编写的。我们建议你按顺序阅读它们。我们同时假设你已经对Angular 2的原理、在[快速开始](../quickstart.html)里介绍的工具、
|
||
[英雄指南](../tutorial)和<code>npm</code>, <code>gulp</code>, and <code>live-server</code>等工具都所有了解。
|
||
|
||
.l-main-section
|
||
:marked
|
||
## Create the test-runner HTML
|
||
## 新建一个测试运行器(test-runner) HTML
|
||
|
||
Locate the folder that contains the application `index.html` for your testing copy of Tour of Heroes.
|
||
|
||
在英雄指南的测试拷贝版里面找到包含`index.html`的目录。
|
||
|
||
Create a new, sibling HTML file, ** `unit-tests.html` ** and copy over the same basic material from the `unit-tests.html` in the [Jasmine 101](./jasmine-testing-101.html) chapter.
|
||
|
||
在隔壁建立一个新的HTML文件,**`unit-tests.html`**,从[Jasmine 101](./jasmine-testing-101.html)章节,将`unit-tests.html`里面的基础内容拷贝到此文件中。
|
||
|
||
+makeExample('testing/ts/unit-tests-2.html', 'test-runner-base', 'unit-tests.html')
|
||
|
||
:marked
|
||
We're picking up right where we left off. All we've done is change the title.
|
||
|
||
我们将从上次的的基础上开始。我们只修改了标题。
|
||
|
||
.l-main-section
|
||
:marked
|
||
## Update `package.json` for testing
|
||
## 为测试更新`package.json`
|
||
|
||
We must install the Jasmine package as well:
|
||
|
||
我们也必须安装Jasmine包:
|
||
|
||
pre.prettyprint.lang-bash
|
||
code npm install jasmine-core --save-dev --save-exact
|
||
|
||
.alert.is-important Be sure to install <code>jasmine-core</code> , not <code>jasmine</code>! 请注意,安装<code>jasmine-core</code>,而不是<code>jasmine</code>!
|
||
|
||
:marked
|
||
Let's make one more change to the `package.json` script commands.
|
||
|
||
让我们在`package.json`的脚本命令部分再修改一项。
|
||
|
||
**Open the `package.json` ** and scroll to the `scripts` node and add in a new one:
|
||
|
||
**打开`package.json`**,滚动到`scripts`节点,添加下面这行:
|
||
|
||
code-example(format="").
|
||
"test": "live-server --open=unit-tests.html"
|
||
|
||
:marked
|
||
That command will launch `live-server` and open a browser to the `unit-tests.html` page we just wrote.
|
||
|
||
该指令将启动`live-server`,并使用浏览器,打开我们刚新建的`unit-tests.html`页面。
|
||
|
||
.l-main-section
|
||
:marked
|
||
## First app tests
|
||
## 第一个应用程序测试
|
||
|
||
We can start testing *some* of our app right away. For example, we can test the `Hero` interface:
|
||
|
||
我们可以立刻开始测试我们应用程序的*一些*代码。比如,我们可以测试`Hero`接口:
|
||
|
||
+makeExample('testing/ts/app/hero.ts')
|
||
|
||
:marked
|
||
Let's add a couple of simple tests in a new file.
|
||
|
||
让我们在一个新文件里面添加一些简单的测试。
|
||
|
||
+makeExample('testing/ts/app/hero.spec.ts', 'base-hero-spec')
|
||
|
||
:marked
|
||
That's the basic Jasmine we learned back in "Jasmine 101".
|
||
|
||
上面是我们在“Jasmine 101”学习的基本Jasmine知识。
|
||
|
||
Notice that we surrounded our tests with ** `describe('Hero')` **.
|
||
|
||
请注意,我们把我们的测试围在**`describe('Hero')`**里面。
|
||
|
||
**By convention, our test always begin with a `describe` that identifies the application part under test.**
|
||
|
||
**按惯例,我们的测试都已`describe`开始,它是用来标识该测试是用来测试哪个应用程序部分的。
|
||
|
||
The description should be sufficient to identify the tested application part and its source file.
|
||
Almost any convention will do as long as you and your team follow it consistently and are never confused.
|
||
|
||
这个说明应该能足够标识被测试应用程序部分和相关源代码。几乎任何
|
||
|
||
But we haven't saved this test yet.
|
||
|
||
.l-main-section
|
||
:marked
|
||
## Where do tests go?
|
||
|
||
Some people like to keep their tests in a `tests` folder parallel to the application source folder.
|
||
|
||
We are not those people. We like our unit tests to be close to the source code that they test. We prefer this approach because
|
||
- The tests are easy to find
|
||
- We see at a glance if an application part lacks tests.
|
||
- Nearby tests can teach us about how the part works; they express the developers intention and reveal how the developer thinks the part should behave under a variety of circumstances.
|
||
- When we move the source (inevitable), we remember to move the test.
|
||
- When we rename the source file (inevitable), we remember to rename the test file.
|
||
|
||
We can't think of a downside. The server doesn't care where they are. They are easy to find and distinguish from application files when named conventionally.
|
||
|
||
.l-sub-section
|
||
:marked
|
||
You may put your tests elsewhere if you wish.
|
||
We're putting ours inside the app, next to the source files that they test.
|
||
|
||
.l-main-section
|
||
:marked
|
||
## First spec file
|
||
|
||
**Create** a new file, ** `hero.spec.ts` ** in `app` next to `hero.ts`.
|
||
|
||
Notice the ".spec" suffix in the test file's filename, appended to the name of the file holding the application part we're testing.
|
||
|
||
.alert.is-important All of our unit test files follow this .spec naming pattern.
|
||
|
||
:marked
|
||
Save the tests we just made in `hero.spec.ts`:
|
||
|
||
+makeExample('testing/ts/app/hero.spec.ts', 'base-hero-spec')
|
||
|
||
:marked
|
||
### Import the part we're testing
|
||
|
||
We have an `import {Hero} from './hero' ` statement.
|
||
|
||
If we forgot this import, a TypeScript-aware editor would warn us, with a squiggly red underline, that it can't find the definition of the `Hero` interface.
|
||
|
||
### Update unit-tests.html
|
||
|
||
Next we update the `unit-tests.html` with a reference to our new `hero.spec.ts` file. Delete the inline test code. The revised pertinent HTML looks like this:
|
||
|
||
+makeExample('testing/ts/unit-tests-2.html', 'load-hero-and-spec')(format=".")
|
||
|
||
:marked
|
||
### Run and Fail
|
||
|
||
Look over at the browser (live-server will have reloaded it). The browser displays
|
||
|
||
figure.image-display
|
||
img(src='/resources/images/devguide/first-app-tests/Jasmine-not-running-tests.png' style="width:400px;" alt="Jasmine not running any tests")
|
||
|
||
:marked
|
||
That's Jasmine saying "**things are _so_ bad that _I'm not running any tests_.**"
|
||
|
||
Open the browser's Developer Tools (F12, Ctrl-Shift-i). There's an error:
|
||
|
||
code-example(format="" language="html").
|
||
Uncaught ReferenceError: System is not defined
|
||
|
||
.l-main-section
|
||
:marked
|
||
## Load tests with SystemJS
|
||
|
||
The immediate cause of the error is the `export` statement in `hero.ts`.
|
||
That error was there all along.
|
||
It wasn't a problem until we tried to `import` the `Hero` interface in our tests.
|
||
|
||
Our test environment lacks support for module loading.
|
||
Apparently we can't simply load our application and test scripts like we do with 3rd party JavaScript libraries.
|
||
|
||
We are committed to module loading in our application.
|
||
Our app will call `import`. Our tests must do so too.
|
||
|
||
We add module loading support in four steps:
|
||
|
||
1. add the *SystemJS* module management library
|
||
1. configure *SystemJS* to look for JavaScript files by default
|
||
1. import our test files
|
||
1. tell Jasmine to run the imported tests
|
||
|
||
These steps are all clearly visible, in exactly that order, in the following lines that
|
||
replace the `<body>` contents in `unit-tests.html`:
|
||
|
||
+makeExample('testing/ts/unit-tests-3.html', 'systemjs')(format=".")
|
||
|
||
:marked
|
||
Look in the browser window. Our tests pass once again.
|
||
|
||
figure.image-display
|
||
img(src='/resources/images/devguide/first-app-tests/test-passed-once-again.png' style="width:400px;" alt="Tests passed once again")
|
||
|
||
|
||
.l-main-section
|
||
:marked
|
||
## Observations
|
||
|
||
### System.config
|
||
System.js demands that we specify a default extension for the filenames that correspond to whatever it is asked to import.
|
||
Without that default, it would translate an import statement such as `import {Hero} from './hero'` to a request for the file named `hero`.
|
||
Not `hero.js`. Just plain `hero`. Our server error with "404 - not found" because it doesn't have a file of that name.
|
||
|
||
Once configured with a default extension of 'js', SystemJS requests `hero.js` which *does* exist and is promptly returned by our server.
|
||
|
||
### Asynchronous System.import
|
||
The call to `System.import` shouldn't surprise us but it's asynchronous nature might.
|
||
If we ponder this for a moment, we realize that it must be asynchronous because
|
||
System.js may have to fetch the corresponding JavaScript file from the server.
|
||
Accordingly, `System.import` returns a promise and we must wait for that promise to resolve.
|
||
Only then can Jasmine start evaluating the imported tests.
|
||
|
||
### window.onload
|
||
Jasmine doesn't have a `start` method. It wires its own start to the browser window's `load` event.
|
||
That makes sense if we're loading our tests with script tags.
|
||
The browser raises the `load` event when it finishes loading all scripts.
|
||
|
||
But we're not loading test scripts inline anymore.
|
||
We're using the SystemJS module loader and it won't be done until long after the browser raised the `load` event.
|
||
Meanwhile, Jasmine started and ran to completion … with no tests to evaluate … before the import completed.
|
||
|
||
So we must wait until the import completes and only then call the window `onLoad` handler.
|
||
Jasmine re-starts, this time with our imported test queued up.
|
||
|
||
.l-main-section
|
||
:marked
|
||
## What's Next?
|
||
We are able to test a part of our application with simple Jasmine tests.
|
||
The part was a stand-alone interface that made no mention or use of Angular.
|
||
|
||
That's not rare but it's not typical either.
|
||
Most of our application parts make some use of the Angular framework.
|
||
Let's test a *pipe* class that does rely on Angular.
|