2018 年 11 月

第 33 卷,第 11 期

孜孜不倦的程序员 - 怎样算是严苛:定向测试

通过Ted Neward |2018 年 11 月

Ted Neward我们又见面了,MEAN 用户们。

幸运的是,"辩论"围绕单元测试的代码不再需要讨论,作为开发人员,您应该检查的方式自动进行编写,毫无疑问,测试代码。参数可能会继续关于是否这些测试应来自之前或之后的代码,这样一来,但很显然,测试不再现代软件项目的可选部分。这然后,当然,将引发问题:如何测试 Angular 应用程序?我简要介绍返回中 2017 年 5 月问题时我刚开始构建一些 Angular 组件,但却几乎不广泛的测试文件 (msdn.com/magazine/mt808505)。现在就可以进行深入查看。

返回到其中

让我们返回到应用程序的开始步骤。当运行"ng 新"搭建基架,出应用程序的初始阶段时,创建所有必要的工具和挂钩和文件到位,才能确保可以测试应用程序。事实上,紧接着"新 ng",而无需进行任何文件,甚至一项更改可以运行"ng test"为运行测试运行程序,后者将执行针对已搭建基架出的测试框架代码编写的代码。

当运行时,"npm test"会启动 Karma,测试运行程序,这会启动浏览器实例,然后运行该实例内的测试。Karma 继续运行,在浏览器保持打开的 WebSocket,以便可以立即测试对源代码文件的任何更改、 删除一些测试,使我们更接近于-no-wait 代码和测试周期的开销。项目基架提供了三项测试,以测试相应的"app.component.ts"代码"app.component.spec.ts"文件编码。作为一般规则,Angular 应用程序中的每个组件应具有相应"。 spec.ts"文件来保存所有测试。如果通过 Angular CLI 创建每个组件,它通常将生成相应的测试文件中,有一些例外 (例如"类"),将需要使用"-规范"到"生成"命令,从而创建规范 (简称"规范") 文件的参数。

实际上,(当然),让我们生成一个快速的类,演讲者,然后为其编写一些测试。像往常一样,使用 Angular CLI 创建该组件 ("ng 生成类演讲者-规范"),并编辑"speaker.ts"文件以包含具有五个构造函数的公共属性的简单类:

export class Speaker {
  constructor(public id: number,
    public firstName: string,
    public lastName: string,
    public age: number,
    public bio?: string,
    )
  { }
}

对应测试应该演练的各种属性,以确保它们按预期方式工作:

import { Speaker } from './speaker';
describe('Speaker', () => {
  it('should create an instance', () => {
    expect(new Speaker(1, "Ted", "Neward", 47, "Ted is a big geek")).toBeTruthy();
  });
  it('should keep data handy', () => {
    var s = new Speaker(1, "Ted", "Neward", 47, "Ted is a big geek");
    expect(s.firstName).toBe("Ted");
    expect(s.firstName).toEqual("Neward");
    expect(s.age).toBeGreaterThan(40);   
  })
});

随着类获得的更复杂、 更复杂的测试应添加,以及。很多测试的强大功能在于"预期"的框架,它提供了大量的"toBe"方法来测试各种方案。在此处查看此操作,包括"toEqual,"执行相等性测试和"toBeGreaterThan"会执行完全按照其名称所示几种的版本。

但是,那些在家里,沿以下但是,会看到有问题 — 保存规范文件后,打开浏览器实例变为红色,指出"Ted"不"Neward"相同的难以理解阴影 !果然,是一个 bug,在第二个"预期"语句,想要比较"firstName",但应为"lastName。 这是很好,,因为时测试代码正在测试的代码中的 bug,它也是该 bug 有时是在测试中,这种情况,并尽快获取反馈写入测试可帮助避免测试程序错误。

更多测试

当然,Angular 应用的多个简单的类; 组成它还可能包括服务,通常是非常简单,若要测试,因为他们往往会提供行为和非常少的状态。如果是单独使用,如格式设置或一些简单的数据转换提供某些行为的服务,测试既简单又让人联想到测试类中发表演讲。服务也经常需要与以某种方式 (例如发出 HTTP 请求,如 SpeakerService 做少量的列返回),其周围世界交互,但这意味着其进行测试变得复杂一点,如果不想必须包括依赖项。实际发送请求通过 HTTP,例如,将使受网络与众不同之处是测试通信或服务器停机,这将产生一些假负失败并使这些测试不具有确定性。这将是错误。

正是出于这种情况下,Angular 使得这种大量依赖关系注入的使用。

例如,让我们开始 SpeakerService 并未进行任何 HTTP 请求的版本中所示图 1

图 1 未发出任何 HTTP 请求的 SpeakerService 类

@Injectable()
export class SpeakerService {
  private static speakersList : Speaker[] = [
    new Speaker(1, "Ted", "Neward", 47,
      "Ted is a big geek living in Redmond, WA"),
    new Speaker(2, "Brian", "Randell", 47,
      "Brian is a high-profile speaker and developer of 20-plus years.
      He lives in Southern California."),
    new Speaker(3, "Rachel", "Appel", 39,
      "Rachel is known for shenanigans the world over. She works for Microsoft."),
    new Speaker(4, "Deborah", "Kurata", 39,
      "Deborah is a Microsoft MVP and Google Developer Expert in Angular,
      and works for the Google Angular team."),
    new Speaker(5, "Beth", "Massi", 39,
      "Beth single-handedly rescued FoxPro from utter obscurity
      and currently works for Microsoft on the .NET Foundation.")
  ]
  public getSpeakers() : Speaker[] { return SpeakerService.speakersList; }
  public getSpeakerById(id : number) : Speaker {
    return SpeakerService.speakersList.find( (s) => s.id == id);
  }
}

此版本是一件小事,若要测试,因为它是同步的不需要外部依赖项,如中所示图 2

图 2 测试 SpeakerService 类

describe('SpeakerService', () => {
  beforeEach(() => {
    TestBed.configureTestingModule({
      providers: [SpeakerService]
    });
  });
  it('should be able to inject the SpeakerService', inject([SpeakerService],
    (service: SpeakerService) => {
    expect(service).toBeTruthy();
  }));
  it('should be able to get a list of Speakers', inject([SpeakerService],
    (service: SpeakerService) => {
    expect(service.getSpeakers()).toBeDefined();
    expect(service.getSpeakers().length).toBeGreaterThan(0);
  }));
});

请注意,每个测试中的"注入"的调用?这基本上就是 Angular 如何管理在测试环境中; 依赖关系注入它是什么,可提供兼容的服务后端 (真实或模拟) 到环境的任何排序。

通常情况下,该服务会很少个以上的中的简单 SpeakerService,并很困难,必须注释掉和/或替换一个不执行任何操作,因此这是在其中使用"模拟"服务更好地工作的虚设"真实"。Angular 具有称为"监视",可以将自身插入到常规服务并替代某些方法,以提供模拟的结果的有用构造:

it('should be able to get a list of Speakers',
    inject([SpeakerService], (service: SpeakerService) => {
  spy = spyOn(service, 'getSpeakers').and.returnValues([]);
  var speakers: Speaker[] = service.getSpeakers();
  expect(speakers).toBeDefined();
  expect(speakers.length).toBe(0);
}));

通过使用 Spy,你可以"替代"正在测试中调用以便提供你想要返回任何的值的方法。

组件测试

生成 Angular 应用程序,但是,很大一部分构建可以出现在该页上的组件,必须能够过测试。若要获取更好地了解测试可视组件,让我们开始打开/关闭切换开关类型组件的简单:

@Component({
  selector: 'app-toggle',
  template: `<button (click)="clicked()">
    I am {{isOn ? "on" : "off" }} -- Click me!
  </button>`,
  styleUrls: ['./toggle.component.css']
})
export class ToggleComponent {
  public isOn = false;
  public clicked() { this.isOn = !this.isOn; }
}

若要测试这一点,可以按原义完全忽略 DOM 并调用各种操作时,只需查看组件的状态:

it('should toggle off to on and off again', () => {
  const comp = new ToggleComponent();
  expect(comp.isOn).toBeFalsy();
  comp.clicked();
  expect(comp.isOn).toBeTruthy();
  comp.clicked();
  expect(comp.isOn).toBeFalsy();
});

这不会检查 DOM,但是,这可以隐藏某些关键 bug。只是因为"isOn"属性已更改并不意味着该模板已呈现该属性是否正确,例如。若要检查为此,可以获取装置,创建的组件实例,并检查 DOM 呈现它,如下所示:

it ('should render HTML correctly when clicked', () => {
  expect(fixture.componentInstance.isOn).toBeFalsy();
  const b = fixture.nativeElement.querySelector("button");
  expect(b.textContent.trim()).toEqual("Click me! I am OFF");
  fixture.componentInstance.clicked();
  fixture.detectChanges();
  expect(fixture.componentInstance.isOn).toBeTruthy();
  const b2 = fixture.nativeElement.querySelector("button");
  expect(b2.textContent.trim()).toEqual("Click me! I am ON");
});

"NativeElement"此处获取该组件的 DOM 节点,我使用"querySelector"执行 jQuery 样式查询来查找相关的 DOM 节点内,在这种情况下,该按钮切换键创建。从这里,我获取其文本内容 (和修整它,因为前面演示的代码行中断会很繁琐,若要在测试中复制的两个位置) 并将其与预期的结果进行比较。但是,请注意,我"单击"组件后,没有调用"detectChanges";这是因为 Angular 需要告诉他们去处理 DOM 相对更改可能会导致事件处理程序,例如,更新该模板中的内插的字符串。如果没有,则测试将失败尽管完美工作在浏览器中的组件。(我犯此确切编写的文章,事实上,因此不要觉得不舒服,如果你忘记了检测所做的更改时。) 请注意,是否该组件不在其 onInit 方法任何重要的初始化,测试将需要 detectChanges 之前执行任何有意义的工作,出于同样的原因。

总结

请记住,顺便说一下,所有这些测试的代码是针对应用程序不服务器端的客户端。还记得我编写了提供 Api 将数据存储到数据库,等等的所有 Express 代码吗?所有这些"外部"这种框架,基本上就是,因此需要维护并单独运行。可以使用一些,我讨论了运行这两个服务器端的"生成"工具和客户端的测试作为更大的一部分测试周期,并因此确保它们会触发对客户端或服务器的任何更改的一部分。Angular 还支持"E2E"(的缩写"端到端") 测试,这是什么谈此处,只用于支持完全这种情况下的作用域之外。

祝您工作愉快!


Ted Neward位于西雅图的 polytechnology 公司顾问、 讲师兼导师,目前担任工程设计和开发者关系主管Smartsheet.com。他撰写了大量文章,创作和与人合著过十几本书,并在世界各地发表演讲。可通过 ted@tedneward.com 与他联系,也可阅读他的博客 blogs.tedneward.com

衷心感谢以下技术专家对本文的审阅:Garvice Eakins (Smartsheet.com)


在 MSDN 杂志论坛讨论这篇文章