22、全面解析 MEAN 应用测试:从基础到实践

全面解析 MEAN 应用测试:从基础到实践

1. 引言

随着应用程序变得越来越庞大和复杂,手动验证代码变得极为困难,因此自动化测试变得至关重要。在 JavaScript 领域,新的工具和测试框架的出现使得测试 Web 应用程序变得更加容易。本文将介绍如何使用现代测试框架和流行工具来测试 MEAN(MongoDB、Express、Angular、Node.js)应用程序。

2. JavaScript 测试概述

在过去几年中,JavaScript 从一个简单的脚本语言发展成为服务器和浏览器中复杂架构的支柱。然而,这也使得开发者面临着手动管理大量未经过自动化测试的代码的挑战。幸运的是,JavaScript 社区开发了许多工具和测试框架来解决这个问题。

在 JavaScript 测试中,主要有两种类型的测试:
- 单元测试 :用于验证孤立代码单元的功能。开发者应尽量编写覆盖应用程序最小可测试部分的单元测试。例如,验证 ORM 方法是否正常工作并输出正确的验证错误。
- 端到端(E2E)测试 :用于验证跨应用程序的功能。这些测试通常需要使用多种工具,并覆盖应用程序的不同部分,包括 UI、服务器和数据库组件。例如,验证注册过程的 E2E 测试。

3. TDD、BDD 和单元测试
  • 测试驱动开发(TDD) :由软件工程师 Kent Beck 开发的软件开发范式。在 TDD 中,开发者首先编写一个(最初失败的)测试,定义对孤立代码单元的期望,然后实现最少的代码使测试通过。测试通过后,开发者清理代码并验证所有测试是否仍然通过。TDD 周期如下:
graph LR
    A[编写失败的测试] --> B[实现最少代码使测试通过]
    B --> C[清理代码并验证所有测试]
    C --> A
  • 行为驱动开发(BDD) :是 TDD 的一个子集,由 Dan North 创建。BDD 帮助开发者确定单元测试的范围,并以行为术语表达测试过程。TDD 为编写测试提供框架,而 BDD 提供编写测试方式的词汇。通常,BDD 测试框架为开发者提供一组自解释的方法来描述测试过程。
4. 测试相关工具
  • 测试框架 :虽然可以使用自己的库编写测试,但这通常不可扩展且需要构建复杂的基础设施。因此,出现了许多流行的测试框架,它们提供了封装测试的方法和运行测试并将结果与其他工具集成的 API。
  • 断言库 :测试框架通常缺乏实际测试表示测试结果的布尔表达式的能力。因此,社区开发了许多断言库,用于检查特定谓词。开发者使用断言表达式来指示在测试上下文中应该为真的谓词。当运行测试时,断言被评估,如果为假,测试失败。
  • 测试运行器 :是使开发者能够轻松运行和评估测试的实用工具。测试运行器通常使用定义的测试框架和一组预配置的属性在不同上下文中评估测试结果。例如,可以配置测试运行器以不同的环境变量运行测试,或在不同的测试平台(通常是浏览器)上运行相同的测试。
5. 测试 Express 应用程序

在 MEAN 应用程序的 Express 部分,业务逻辑主要封装在控制器中,同时还有 Mongoose 模型用于处理数据操作和验证。为了全面覆盖 Express 应用程序代码,需要编写测试来覆盖模型和控制器。将使用 Mocha 作为测试框架,Should.js 作为模型的断言库,SuperTest 作为控制器的 HTTP 断言库。

5.1 引入 Mocha

Mocha 是由 Express 创造者 TJ Holowaychuk 开发的多功能测试框架。它支持 BDD 和 TDD 单元测试,使用 Node.js 运行测试,并允许开发者运行同步和异步测试。Mocha 结构简单,不包含内置的断言库,但支持集成流行的断言框架。它还提供了一组不同的报告器来呈现测试结果,并具有许多功能,如挂起测试、排除测试和跳过测试。

Mocha 的 BDD 接口包括以下描述性方法:
| 方法 | 描述 |
| ---- | ---- |
| describe(description, callback) | 用描述包装每个测试套件,回调函数用于定义测试规范或子套件 |
| it(description, callback) | 用描述包装每个测试规范,回调函数用于定义实际测试逻辑 |
| before(callback) | 在测试套件中的所有测试之前执行一次的钩子函数 |
| beforeEach(callback) | 在测试套件中的每个测试规范之前执行的钩子函数 |
| after(callback) | 在测试套件中的所有测试执行后执行一次的钩子函数 |
| afterEach(callback) | 在测试套件中的每个测试规范执行后执行的钩子函数 |

5.2 引入 Should.js

Should.js 是由 TJ Holowaychuk 开发的库,旨在帮助开发者编写可读和富有表现力的断言表达式。它扩展了 Object.prototype ,允许开发者表达对象应该如何行为。Should.js 的一个强大功能是每个断言返回一个包装对象,因此断言可以链式调用。例如:

user.should.be.an.Object.and.have.property('name', 'tj');
5.3 引入 SuperTest

SuperTest 是另一个由 TJ Holowaychuk 开发的断言库,它通过提供一个抽象层来进行 HTTP 断言,与其他断言库不同。它帮助开发者创建测试 HTTP 端点的断言表达式,从而覆盖暴露给浏览器的代码。例如:

request(app).get('/user')
  .set('Accept', 'application/json')
  .expect('Content-Type', /json/)
  .expect(200, done);
6. 安装和配置测试环境
6.1 安装 Mocha

使用以下命令全局安装 Mocha:

$ npm install –g mocha
6.2 安装 Should.js 和 SuperTest

修改项目的 package.json 文件如下:

{
  "name": "MEAN",
  "version": "0.0.10",
  "scripts": {
    "tsc": "tsc",
    "tsc:w": "tsc -w",
    "app": "node server",
    "start": "concurrently \"npm run tsc:w\" \"npm run app\" ",
    "postinstall": "typings install"
  },
  "dependencies": {
    "@angular/common": "2.1.1",
    "@angular/compiler": "2.1.1",
    "@angular/core": "2.1.1",
    "@angular/forms": "2.1.1",
    "@angular/http": "2.1.1",
    "@angular/platform-browser": "2.1.1",
    "@angular/platform-browser-dynamic": "2.1.1",
    "@angular/router": "3.1.1",
    "body-parser": "1.15.2",
    "core-js": "2.4.1",
    "compression": "1.6.0",
    "connect-flash": "0.1.1",
    "connect-mongo": "1.3.2",
    "cookie-parser": "1.4.3",
    "ejs": "2.5.2",
    "express": "4.14.0",
    "express-session": "1.14.1",
    "method-override": "2.3.6",
    "mongoose": "4.6.5",
    "morgan": "1.7.0",
    "passport": "0.3.2",
    "passport-facebook": "2.1.1",
    "passport-google-oauth": "1.0.0",
    "passport-local": "1.0.0",
    "passport-twitter": "1.0.4",
    "reflect-metadata": "0.1.8",
    "rxjs": "5.0.0-beta.12",
    "socket.io": "1.4.5",
    "systemjs": "0.19.39",
    "zone.js": "0.6.26"
  },
  "devDependencies": {
    "concurrently": "3.1.0",
    "should": "11.1.1",
    "supertest": "2.0.1",
    "traceur": "0.0.111",
    "typescript": "2.0.3",
    "typings": "1.4.0"
  }
}

然后在项目根目录下运行以下命令安装依赖:

$ npm install
6.3 配置测试环境

由于测试可能涉及数据库操作,为了安全起见,使用不同的配置文件运行测试。在 config/env 文件夹中创建一个新的配置文件 test.js ,内容如下:

module.exports = {
  db: 'mongodb://localhost/mean-book-test',
  sessionSecret: 'Your Application Session Secret',
  viewEngine: 'ejs',
  facebook: {
    clientID: 'APP_ID',
    clientSecret: 'APP_SECRET',
    callbackURL: 'http://localhost:3000/oauth/facebook/callback'
  },
  twitter: 
  {
    clientID: 'APP_ID',
    clientSecret: 'APP_SECRET',
    callbackURL: 'http://localhost:3000/oauth/twitter/callback'
  },
  google: {
    clientID: 'APP_ID',
    clientSecret: 'APP_SECRET',
    callbackURL: 'http://localhost:3000/oauth/google/callback'
  }
};

最后,在 app 文件夹中创建一个名为 tests 的新文件夹,用于存放测试文件。

7. 编写第一个 Mocha 测试

在编写测试之前,需要将 Express 应用程序的组件分解为可测试的单元。由于大部分应用程序逻辑已经分为模型和控制器,因此可以分别测试每个模型和控制器。

例如,测试 Article Mongoose 模型的保存方法,在 app/tests 文件夹中创建一个新文件 article.server.model.tests.js ,内容如下:

const app = require('../../server.js');
const should = require('should');
const mongoose = require('mongoose');
const User = mongoose.model('User');
const Article = mongoose.model('Article');
let user, article;

describe('Article Model Unit Tests:', () => {
  beforeEach((done) => {
    user = new User({
      firstName: 'Full',
      lastName: 'Name',
      displayName: 'Full Name',
      email: 'test@test.com',
      username: 'username',
      password: 'password'
    });
    user.save(() => {
      article = new Article({
        title: 'Article Title',
        content: 'Article Content',
        user: user
      });
      done();
    });
  });

  describe('Testing the save method', () => {
    it('Should be able to save without problems', () => {
      article.save((err) => {
        should.not.exist(err);
      });
    });

    it('Should not be able to save an article without a title', () => {
      article.title = '';
      article.save((err) => {
        should.exist(err);
      });
    });
  });

  afterEach((done) => {
    Article.remove(() => {
      User.remove(() => {
        done();
      });
    });
  });
});

在这个测试中:
- 首先引入模块依赖并定义全局变量。
- 使用 describe() 方法开始测试,表明要测试 Article 模型。
- 在 beforeEach() 方法中创建新的用户和文章对象,并在数据库操作完成后调用 done() 回调通知测试框架继续执行测试。
- 创建一个新的 describe 块,用于测试模型的保存方法。在这个块中,使用 it() 方法创建两个测试:一个测试文章是否可以正常保存,另一个测试没有标题的文章是否不能保存。
- 在 afterEach() 方法中,清理数据库中的文章和用户数据。

全面解析 MEAN 应用测试:从基础到实践

8. 测试 Express 控制器

除了测试模型,还需要测试 Express 控制器,以确保控制器端点能正常工作。这里将使用 SuperTest 来测试控制器的 HTTP 端点。

假设我们有一个 articles 控制器,包含获取文章列表和创建文章的端点。以下是测试控制器的示例代码,在 app/tests 文件夹中创建 article.server.controller.tests.js 文件:

const app = require('../../server.js');
const request = require('supertest');
const should = require('should');
const mongoose = require('mongoose');
const User = mongoose.model('User');
const Article = mongoose.model('Article');
let user, token;

describe('Article Controller Tests:', () => {
  beforeEach((done) => {
    user = new User({
      firstName: 'Full',
      lastName: 'Name',
      displayName: 'Full Name',
      email: 'test@test.com',
      username: 'username',
      password: 'password'
    });
    user.save((err, savedUser) => {
      request(app)
        .post('/api/auth/signin')
        .send({
          username: 'username',
          password: 'password'
        })
        .end((err, res) => {
          token = res.body.token;
          done();
        });
    });
  });

  describe('GET /api/articles', () => {
    it('Should return all articles', (done) => {
      request(app)
        .get('/api/articles')
        .set('Authorization', `Bearer ${token}`)
        .expect(200)
        .end((err, res) => {
          should.not.exist(err);
          res.body.should.be.an.Array();
          done();
        });
    });
  });

  describe('POST /api/articles', () => {
    it('Should create a new article', (done) => {
      const newArticle = {
        title: 'New Article',
        content: 'This is a new article'
      };
      request(app)
        .post('/api/articles')
        .set('Authorization', `Bearer ${token}`)
        .send(newArticle)
        .expect(200)
        .end((err, res) => {
          should.not.exist(err);
          res.body.should.have.property('title', 'New Article');
          done();
        });
    });
  });

  afterEach((done) => {
    Article.remove(() => {
      User.remove(() => {
        done();
      });
    });
  });
});

在这个测试中:
- 在 beforeEach() 方法中创建一个用户并登录,获取令牌。
- 使用 describe() 方法分别测试 GET /api/articles POST /api/articles 端点。
- 在每个测试中,使用 SuperTest 发送 HTTP 请求,并使用断言库验证响应。
- 在 afterEach() 方法中清理数据库中的文章和用户数据。

9. 测试 Angular 应用程序

MEAN 应用程序的 Angular 部分也需要进行测试。这里将介绍如何使用 Karma 测试运行器和 Jasmine 测试框架来进行单元测试和端到端测试。

9.1 安装和配置 Karma

首先,安装 Karma 和相关依赖:

$ npm install karma karma-jasmine jasmine-core karma-chrome-launcher --save-dev

然后,在项目根目录下创建 karma.conf.js 文件,配置如下:

module.exports = function(config) {
  config.set({
    basePath: '',
    frameworks: ['jasmine'],
    files: [
      'node_modules/core-js/client/shim.min.js',
      'node_modules/zone.js/dist/zone.js',
      'node_modules/zone.js/dist/long-stack-trace-zone.js',
      'node_modules/zone.js/dist/proxy.js',
      'node_modules/zone.js/dist/sync-test.js',
      'node_modules/zone.js/dist/jasmine-patch.js',
      'node_modules/zone.js/dist/async-test.js',
      'node_modules/zone.js/dist/fake-async-test.js',
      'src/test.ts'
    ],
    exclude: [],
    preprocessors: {
      'src/test.ts': ['webpack', 'sourcemap']
    },
    webpack: {
      devtool: 'inline-source-map',
      resolve: {
        extensions: ['.ts', '.js']
      },
      module: {
        rules: [
          {
            test: /\.ts$/,
            use: [
              {
                loader: 'awesome-typescript-loader',
                options: { configFileName: 'src/tsconfig.spec.json' }
              },
              'angular2-template-loader'
            ]
          },
          {
            test: /\.html$/,
            loader: 'html-loader'
          },
          {
            test: /\.(png|jpe?g|gif|svg|woff|woff2|ttf|eot|ico)$/,
            loader: 'null-loader'
          },
          {
            test: /\.css$/,
            exclude: /\.component\.css$/,
            loader: 'style-loader!css-loader'
          },
          {
            test: /\.component\.css$/,
            loader: 'raw-loader'
          }
        ]
      }
    },
    webpackServer: {
      noInfo: true
    },
    reporters: ['progress'],
    port: 9876,
    colors: true,
    logLevel: config.LOG_INFO,
    autoWatch: true,
    browsers: ['Chrome'],
    singleRun: false,
    concurrency: Infinity
  });
};
9.2 编写 Angular 单元测试

使用 Jasmine 编写 Angular 组件的单元测试。以下是一个简单的组件测试示例:

import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ArticleComponent } from './article.component';

describe('ArticleComponent', () => {
  let component: ArticleComponent;
  let fixture: ComponentFixture<ArticleComponent>;

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [ ArticleComponent ]
    })
    .compileComponents();
  }));

  beforeEach(() => {
    fixture = TestBed.createComponent(ArticleComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  it('should create', () => {
    expect(component).toBeTruthy();
  });
});

在这个测试中:
- 使用 TestBed 配置测试模块。
- 创建组件实例并检测变化。
- 使用 expect() 方法进行断言。

9.3 编写 Angular 端到端测试

对于 Angular 端到端测试,可以使用 Protractor。首先,安装 Protractor:

$ npm install protractor --save-dev

然后,在项目根目录下创建 protractor.conf.js 文件,配置如下:

exports.config = {
  allScriptsTimeout: 11000,
  specs: [
    './e2e/**/*.e2e-spec.ts'
  ],
  capabilities: {
    'browserName': 'chrome'
  },
  directConnect: true,
  baseUrl: 'http://localhost:4200/',
  framework: 'jasmine',
  jasmineNodeOpts: {
    showColors: true,
    defaultTimeoutInterval: 30000
  },
  onPrepare() {
    require('ts-node').register({
      project: 'e2e/tsconfig.e2e.json'
    });
  }
};

编写一个简单的端到端测试示例,在 e2e 文件夹中创建 article.e2e-spec.ts 文件:

import { browser, by, element } from 'protractor';

describe('Article Page', () => {
  beforeEach(() => {
    browser.get('/articles');
  });

  it('should display article list', () => {
    const articles = element.all(by.css('.article-item'));
    expect(articles.count()).toBeGreaterThan(0);
  });
});

在这个测试中:
- 使用 Protractor 打开指定页面。
- 使用 element() by 选择器查找元素。
- 使用 expect() 方法进行断言。

10. 总结

通过以上步骤,我们全面介绍了如何测试 MEAN 应用程序。包括使用 Mocha、Should.js 和 SuperTest 测试 Express 应用程序的模型和控制器,以及使用 Karma、Jasmine 和 Protractor 测试 Angular 应用程序的单元和端到端功能。

测试是保证应用程序质量和稳定性的重要手段。在开发过程中,应该养成编写测试的习惯,确保代码的每一部分都经过充分的测试。同时,随着项目的发展,不断完善和扩展测试用例,以适应新的功能和变化。

以下是测试 MEAN 应用程序的步骤总结表格:
| 测试部分 | 工具 | 步骤 |
| ---- | ---- | ---- |
| Express 模型 | Mocha、Should.js | 1. 安装 Mocha 和 Should.js
2. 配置测试环境
3. 编写模型测试代码 |
| Express 控制器 | Mocha、SuperTest | 1. 安装 SuperTest
2. 编写控制器测试代码,使用 SuperTest 测试 HTTP 端点 |
| Angular 单元测试 | Karma、Jasmine | 1. 安装 Karma 和 Jasmine
2. 配置 Karma
3. 编写 Angular 组件单元测试代码 |
| Angular 端到端测试 | Protractor | 1. 安装 Protractor
2. 配置 Protractor
3. 编写端到端测试代码 |

通过遵循这些步骤和使用相应的工具,可以有效地测试 MEAN 应用程序,提高应用程序的质量和可靠性。

graph LR
    A[开始] --> B[测试 Express 应用]
    B --> C[测试模型]
    B --> D[测试控制器]
    C --> E[使用 Mocha 和 Should.js]
    D --> F[使用 Mocha 和 SuperTest]
    A --> G[测试 Angular 应用]
    G --> H[单元测试]
    G --> I[端到端测试]
    H --> J[使用 Karma 和 Jasmine]
    I --> K[使用 Protractor]
    E --> L[完成测试]
    F --> L
    J --> L
    K --> L
    L --> M[结束]

这个流程图展示了测试 MEAN 应用程序的整体流程,从开始到完成各个部分的测试,最后结束测试。通过这样的流程,可以确保 MEAN 应用程序的各个部分都得到充分的测试。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值