Академический Документы
Профессиональный Документы
Культура Документы
Daniel Irvine
Foreword by Maaret Pyhäjärvi
Build Your Own Test Framework: A Practical Guide to Writing Better
Automated Tests
Daniel Irvine
London, UK
Acknowledgments������������������������������������������������������������������������������xv
Foreword������������������������������������������������������������������������������������������xvii
Introduction���������������������������������������������������������������������������������������xix
iii
Table of Contents
iv
Table of Contents
v
Table of Contents
Exercises�������������������������������������������������������������������������������������������������������������96
Discussion Questions������������������������������������������������������������������������������������������97
Summary������������������������������������������������������������������������������������������������������������98
vi
Table of Contents
vii
Table of Contents
viii
Table of Contents
ix
Table of Contents
Exercises�����������������������������������������������������������������������������������������������������������238
Discussion Questions����������������������������������������������������������������������������������������239
Summary����������������������������������������������������������������������������������������������������������240
Index�������������������������������������������������������������������������������������������������261
x
About the Author
Daniel Irvine is a freelance software developer
and technical coach. He specializes in
simplifying software codebases and improving
the technical confidence of dev teams. His first
exposure to automated testing was in 2005,
when he was tasked with writing Ruby test
suites for a million-line C++ application. Since
then, he’s been an advocate for developer-
led testing practices. He is the author of Mastering React Test-Driven
Development, now in its second edition.
He can be contacted via his website at www.danielirvine.com.
xi
About the Technical Reviewer
Sourabh Mishra is an entrepreneur, developer,
speaker, author, corporate trainer, and
animator. He is a Microsoft guy; he is very
passionate about Microsoft technologies and
a true .Net Warrior. Sourabh started his career
when he was just 15 years old. He’s loved
computers from childhood. His programming
experience includes C/C++, ASP.Net, C#,
VB.net, WCF, SQL Server, Entity Framework, MVC, Web API, Azure, jQuery,
Highcharts, and Angular. Sourabh has been awarded a Most Valuable
Professional (MVP) status. He has the zeal to learn new technologies and
shares his knowledge on several online community forums.
He is the author of Practical Highcharts with Angular (Apress,
2020), which talks about how you can develop stunning and interactive
dashboards using Highcharts with Angular.
He is the founder of “IECE Digital” and “Sourabh Mishra Notes,” an
online knowledge-sharing platform where one can learn new technologies
very easily and comfortably.
He can be reached via the following platforms:
• YouTube: sourabhmishranotes
• Twitter: sourabh_mishra1
• Facebook: facebook.com/sourabhmishranotes
• Instagram: sourabhmishranotes
• Email: sourabh_mishra1@hotmail.com
You can find his books on Amazon via www.amazon.com/stores/
author/B084DMG1WG.
xiii
Acknowledgments
I would like to thank all the readers of the original Leanpub version of the
book who have made this new edition possible.
The whole team at Apress have been wonderful and made the process
an absolute breeze. Particular thanks go to Divya Modi, the lead editor, and
Shobana Srinivasan, the project coordinator, both of whom were extremely
patient with me as I juggled tasks.
Finally, I am indebted to all of my past and present clients, who have
made my own journey to expertise possible.
xv
Foreword
My first thought on Build Your Own Test Framework was curious: Why
would I want to? The experience I come from is that there’s plenty of test
frameworks to go around, and endless conversations on which of those is
worth choosing, even more so among those working in the software testing
space. I found my answer to this question reading this book.
Moving from choosing a test framework to building a test framework
showed me a great way of leveling learning in the tests space. It guides
focus on what tests are for, what features are useful for tests, what features
are generally available, and how we might build them. Daniel Irvine does
a brilliant job at leveling this learning for the reader. Reinventing a wheel,
you learn a lot about wheels. This is exactly what we need.
Breaking a slightly abstract tool like a test framework into features and
walking us through design and implementation helps us not only build a
test framework but a foundation from which we can contribute on other
test frameworks and understand our own tests better.
Work through the chapters and code along and I’m sure you will enjoy
features such as it.behavesLike to an extent you start missing the concept
in frameworks you may have run into and are inspired to extend your own
framework—and build your own for practice if not production use.
Maaret Pyhäjärvi
Principal Test Engineer at Vaisala Oyj
xvii
Introduction
This book is a follow-along practical exercise in building an automated
unit test framework, written in the JavaScript language and running on
the Node platform. You can think of it as a less functional replacement for
well-known packages like Jest,1 Mocha,2 Jasmine,3 and Vitest.4
The framework is called concise-test, and it takes the form of an
NPM package that you’ll start building in Chapter 1. The book also makes
use of a package named todo-example, which is a sample application that
will make use of concise-test, as a package dependency.
By the end of Chapter 1, you’ll be able to run the command npm test
in your todo-example project directory and have your test runner execute
the application test scripts.
1
https://jestjs.io
2
https://mochajs.org
3
https://jasmine.github.io
4
https://vitest.dev
5
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/
Functions/Arrow_functions
6
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/
Operators/Destructuring_assignment
7
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/
Functions/rest_parameters
8
https://nodejs.org/api/esm.html#modules-ecmascript-modules
xix
Introduction
That being said, don’t stop reading if you don’t know any JavaScript;
as long as you have experience in at least one programming language, you
should be able to pick it up as we go along. If you’re unsure what some of
the syntax means, take a moment to stop and look it up online.
JavaScript is a wonderfully flexible language. It is dynamic and
functional at its core, but also has support for objects in a rather unique
way, which is empowering.
The beauty of unit testing is that it is universally applicable to all
mainstream programming languages, so if you were really keen, you
could rebuild all of the same code in your own favorite language. The only
chapters that are directly related to JavaScript are Chapters 9 and 17.
xx
Introduction
What we’re building here is a spike. That means that, ironically enough,
there are no tests for what we’re about to build. Spikes help us quickly
explore a problem space without getting too bogged down in plumbing code.
The following diagram shows the full extent of the system we’re going
to build.
In Part 1, “Building the Core of a Test Framework,” you will build a
barebones implementation of a test runner that features all the major
components of an xUnit-style test runner.
In Part 2, “Constructing a Usable Framework,” you extend the test
framework to include ergonomic features that make the concise-test
runner into a viable competitor to mainstream test frameworks.
In Part 3, “Extending for Power Users,” we add some unique features
that are borrowed from other programming environments.
In Part 4, “Test Doubles and Module Mocks,” we end the book with a
look at unit testing’s most misunderstood feature, the humble test double.
xxi
Introduction
xxii
Introduction
https://github.com, which is also where the source code for this book
is stored.
You will also need basic command-line knowledge, like how to
navigate between directories with the cd command and how to use the git
and npm commands.
github.com/Apress/Build-your-Own-Test-Framework-by-
Daniel-Irvine
xxiii
Introduction
but the essence of it is the following commands that you’d enter on the
command line:
xxiv
CHAPTER 1
Creating a Barebones
Test Runner
In this chapter, you’ll learn all about test runners: what they are, why they
exist, and how to build one for the Node runtime environment.
At the highest level, the test runner is the piece of software that does
exactly what it says: it runs (or executes) your tests (also known as test
cases). The input to your test runner will be your test files, and its output
will be a test report showing how your test cases performed.
By the end of this chapter, you’ll have learned the following:
What Is an Entrypoint?
Think about how your web browser loads a typical web-based JavaScript
application. You point the browser at a specific URL (like www.example.com),
and that pulls down an HTML page that the browser parses and loads.
This HTML page might contain a script HTML tag that references another
URL: this URL points to a JavaScript source file. And this source file is
special because it marks your application entrypoint: it is the first bit of
code that your browser will fetch, parse, load, and execute.
What sets apart the entrypoint from any other JavaScript file? Well,
your entrypoint source file (or module) may import and load other source
files. This network of files represents your entire application, but only one
of these files is the entrypoint.
The browser in this example represents the runtime environment. The
JavaScript source file is your application, and the entrypoint is very simply
that same file.
Every computer program has an entrypoint, and every computer
program operates within a runtime environment, be it the browser or your
terminal.
It’s the job of the runtime environment to load and execute your code,
as visualized in Figure 1-1.
4
Chapter 1 Creating a Barebones Test Runner
With that context, think about how you might write some automated
tests for any given computer program. By automated test I mean one
that invokes your code, checks the result and expected value match, and
reports that information to you. The only action you need to take is the one
to start the test.
5
Chapter 1 Creating a Barebones Test Runner
6
Chapter 1 Creating a Barebones Test Runner
7
Chapter 1 Creating a Barebones Test Runner
But there are other benefits, like faster test runs, because you’re
not having to execute the entire program for every test run, and test
independence, which means that a bug in one unit won’t affect the tests in
other units, which helps pinpoint where errors lie (instead of every single
test falling over when an error is introduced).
1
The Smalltalk Report, October 1994
2
This is stylized as a Unified Modeling Language (UML) sequence diagram. UML
has gone out of fashion, but I find the core concepts highly valuable.
8
Chapter 1 Creating a Barebones Test Runner
10
Chapter 1 Creating a Barebones Test Runner
Now let’s clone the repository. Open a terminal window and change to
your default workspace directory (for me that’s ~/work), and then use git
clone to pull down the repository to your local machine.
Then, rename the directory to something much shorter, like byotf,
which will make it easier to see long file paths.
Navigate into the Chapter01/Start directory, which is where we’ll be
starting the work in this chapter. You’ll see it has two subdirectories:
- ~/work/byotf/Chapter01/Start
- /concise-test
- /todo-example
cd concise-test
mkdir src
npm init -y
"type": "module",
11
Chapter 1 Creating a Barebones Test Runner
"bin": "./src/cli.mjs",
"exports": {
".": "./src/runner.mjs"
}
"main": "index.mjs"
Save the file and let’s get on with defining the executable file that will
act as our application entrypoint.
#!/usr/bin/env node
3
https://docs.npmjs.com/files/package.json#bin
4
https://nodejs.org/api/esm.html#esm_package_exports
12
Chapter 1 Creating a Barebones Test Runner
run();
On Unix (or Unix-like platforms), you’ll also need to run this command
from your project directory:
Next up, create a new file src/runner.mjs and add the following stub
content:
If you look back at the sequence diagram in Figure 1-3, the code we’ve
written so far is the “Execute” line between the Node and Test runner
lifelines and also (arguably) the returning “Report results” line.
% npm link
5
For more information on how the npm link command works, see the
documentation page: https://docs.npmjs.com/cli/v6/commands/npm-link
13
Chapter 1 Creating a Barebones Test Runner
% cd ../todo-example
% node test/tests.mjs
All being well, there won’t be any output. That means the tests have
been successful. Otherwise, you’d see an exception on-screen.
Let’s bring in our concise-test test runner.
Run the following command:
npm unlink
When you’re done working through this book, you may wish to remove
your local package link so that you can use the published concise-test
from NPM. To do that, use the npm unlink command from both project
directories to delete copies of the package from your local Node repository.
14
Chapter 1 Creating a Barebones Test Runner
Now that it’s linked, we can tell Node that the test command should
run our new executable.
Open the sample application’s package.json and find the scripts
entry that looks like this:
"test": "concise-test"
Let’s test that out: back on the command line, run npm test from
within your sample application directory.
Here’s what I get:
% npm test
hello, world!
% cd ../concise-test
15
Chapter 1 Creating a Barebones Test Runner
In the next few chapters, we’ll be moving back and forth between the
sample application and the test runner project. On my machine these
projects are in two sibling directories: the sample application is todo-
example, and the test runner is concise-test.
If you use the command line, you might want to have two windows
(or tabs) open, one for each project directory. In your editor, use a vertical
split so that you can see the sample application tests (todo-example/test/
tests.mjs) on one side and the test runner code (concise-test/src/
runner.mjs) on the other.
If you’re using an IDE, you might also want two copies of the IDE open,
one for each project, or you can try to open all files in the same window
and use the cd command in the IDE terminal window, as necessary.
Okay, let’s get back to the test runner implementation. In the concise-
test project test directory, open src/runner.mjs and replace the entire
contents with this implementation.
What this code does is find the file test/tests.mjs in the running
location and load it into memory.
Let’s try that out now. Switch back to the application library and run
npm test:
% npm test
16
Chapter 1 Creating a Barebones Test Runner
Beautiful! But… how do we know if our tests actually ran? Only one
way to find out… by breaking a test!
17
Chapter 1 Creating a Barebones Test Runner
Now, what would you expect to happen when npm test is run?
I’d expect the test runner to return an error with a stack trace and this
message:
% npm test
> todo-example@1.0.0
> concise-test
18
Chapter 1 Creating a Barebones Test Runner
19
Chapter 1 Creating a Barebones Test Runner
While this isn’t perfect—there’s a lot of noise from the stack trace
here—you can see a couple of important things.
First, the expected error message is shown on-screen. We will use the
exception messages as the basis for our test expectations that we’ll cover in
Chapter 4.
Second, the very last line is the Test run finished output, so we
know that our try-catch block has caught the error and suppressed it. Our
test runner is in control. As you work through Chapter 2, you’ll improve
this last line by including test summary information.
Committing to Git
Now is a great time to commit your work. If you’re using the command
line, you can use the following commands:
% git init
% git commit -m 'Complete chapter 1'
6
GitHub’s default .gitignore file has a whole list of ignored files. I tend to start
with a blank file with a single entry: node_modules.
20
Chapter 1 Creating a Barebones Test Runner
Discussion Questions
1. Imagine you’re working in a programming language
that doesn’t support exported symbols, only an
entrypoint. How would you build an automated test
runner that could execute test cases against units
within your codebase? Draw a sequence diagram to
show how execution flows from start to finish of a
single test run.
Summary
This chapter has covered the theory behind application entrypoints and
test runners. You created a barebones implementation of a test runner,
which looks for a single test file, test/tests.mjs, and prints out the first
error that’s thrown before bombing out.
This is a great place to start building our first test function, it, which is
exactly what we’ll do in Chapter 2.
21
CHAPTER 2
Building a Function to
Represent a Test Case
In this chapter we’ll build a function that encapsulates a single test case.
It’s called it and it looks like this:
Calls to it have two parts: the description and the test code. We’ll
see how this function can be implemented as the first major piece of the
concise-test test runner.
By the end of the chapter, you’ll have learned
• The history of the it function
The it Function
How about we start with the name? Why do we call it “it” and not “test”?
The name dates back to 2007 when “it” was introduced as a part of the
RSpec tool. At that time, Behavior-Driven Development (BDD) was the
new “big idea” in testing.
One of the driving forces behind BDD is the idea that people frequently
misunderstand what test means, and so they attempted to avoid using
that word.
What really is a test?
The “it” that it refers to is the structure under test. That “structure” could
be a function, a class, or a component. It’s the thing that’s being tested.
I prefer it over test because it immediately puts you in the right
frame of mind for describing an action: what does this thing do?
Sometimes, software is specified via formal(ish) specifications that
describe behavior with words like should, will, may, and so on.1 And this
was often the way that BDD tests were written:
I don’t see much point in the “should”2—it’s a bit too wordy for my
liking. Why not just use a simple, present-tense verb?
In the concise-test test runner, the first word of the test description
should be a verb, so that the it reads in plain English: it describes a test.
Here are some examples:
adds two numbers prints out a string renders an error message
dispatches an action
Concise, isn’t it?
1
For example, see RFC 2119, from March 1997, at https://datatracker.ietf.
org/doc/html/rfc2119
2
It has some use if you think about the negative test cases, which would read: it
should not…
24
Chapter 2 Building a Function to Represent a Test Case
The clear leader is the verb “returns.” How boring! But it makes sense
when you think that most of the tests are simply testing the return values of
3
https://github.com/PacktPublishing/Mastering-React-Test-Driven-
Development-second-edition
25
Chapter 2 Building a Function to Represent a Test Case
functions. And the next most frequent test verb is “renders,” which makes
sense given that it’s a React codebase.
That’s the theory over. Next, it’s time to write our implementation of
the it function.
This is nowhere close to being the final definition of it, but this
starting point gives us something important: a new scoped context for our
variables.
Now go back to the sample project and open test/tests.mjs. First, at
the top of the file, add an import for it:
26
Chapter 2 Building a Function to Represent a Test Case
Try to run this test (using npm test from the sample application
directory) to verify that the import has worked. If you get an error about
the “main” entrypoint, go back to Chapter 1 and ensure that you ran the
instructions in the section “Setting Package Configuration.”
Next, take the first test, which should be lines 5–8 if you copied out the
previous code sample to the letter, and wrap it in a call to the it function.
if (!markAsDone(todo).completedAt)
throw new Error(
"completedAt not set when calling markAsDone"
);
});
Already this has made a huge difference to the readability of our test.
Let’s carry on with the next test grouping.
try {
repository.add(emptyTodo());
throw new Error(
"no error thrown when adding an empty todo"
);
} catch (e) {
if (e.message !== "title cannot be blank")
27
Another random document with
no related content on Scribd:
1403. Cp. Metam. xv. 27.
1413 f. Pont. i. 3. 57 f.
1420. Cp. Her. iii. 24, used here with a change of meaning.
1424. Cp. Ars Amat. ii. 88, ‘Nox oculis pavido venit oborta metu.’
1425 f. Pont. i. 2. 45 f.
1429 f. Cp. Pont. i. 2. 49 f.
1433. Metam. iii. 709.
1442. Cp. Her. v. 14, where we have ‘Mixtaque’ instead of
‘Copula.’
1445 ff. Cp. Metam. xiv. 214-216.
1453. Adapted from Metam. iv. 263, ‘Rore mero lacrimisque suis
ieiunia pavit.’ The change of ‘mero’ to ‘meo’ involves a tasteless
alteration of the sense, while the sound is preserved.
1459. Cp. Rem. Amoris, 581.
1465. Metam. ii. 656. Our author has borrowed the line without
supplying an appropriate context, and the result is nonsense. Ovid
has
‘Suspirat ab imis
Pectoribus, lacrimaeque genis labuntur obortae.’
1467 f. Pont. i. 2. 29 f.
1469. Cp. Metam. xiii. 539.
1473. Ovid, Metam. viii. 469.
1475. Metam. iv. 135, borrowed without much regard to the
context.
1485. From Ovid, Her. xiv. 37, where however we have ‘calor,’
not ‘color,’ a material difference.
1496. Her. v. 46.
1497. The expression ‘verbis solabar amicis’ is from Ovid (Fasti,
v. 237), but here ‘solabar’ seems to be made passive in sense.
1501 f. i.e. ‘cessat amor eius qui prius,’ &c., with a rather harsh
ellipse of the antecedent. The couplet is a parody of Ovid, Pont. iv. 6.
23 f.,
LIB. II.
With the general drift of what follows cp. Conf. Amantis, Prol. 529
ff.
1. Incausti specie, cp. Conf. Amantis, viii. 2212.
18. nos: meaning the people of England, as compared with those
of other countries.
31 f. Cp. Ovid, Tristia, v. 8. 19 f.
33. Tristia, v. 5. 47.
41. Job v. 6, ‘Nihil in terra sine causa fit’: cp. Mirour de l’Omme,
26857.
59. This is the usual opposition of rose and nettle, based perhaps
originally on Ovid, Rem. Amoris, 46: cp. Conf. Amantis, ii. 401 ff.
67 f. Cp. Boethius, Consol. Phil. 2 Pr. 4, ‘in omni adversitate
fortunae infelicissimum genus est infortunii fuisse felicem.’ So Dante,
Inf. v. 121 ff.,
117 ff. Cp. Ovid, Her. v. 109 ff. In l. 117 ‘siccis’ is substituted, not
very happily, for ‘suci.’
138. Cp. Conf. Amantis, Latin Verses after ii. 1878,
LIB. III.
1-28. The form of these lines which stood originally in S is given
by the Trinity College, Dublin, and the Hatfield MSS. The passage
has been rewritten over erasure in CHG, and it must be left doubtful
what text they had originally. From the fact that the erasure in G
begins with the second line, it may seem more probable that the
original text of this manuscript agreed with that which we have now
in S, rather than with TH₂: for in the latter case there would have
been no need to begin the erasure before l. 4. In CH the whole
passage has been recopied (the same hand appearing here in the
two MSS.) so that we can draw no conclusion about the point where
divergence actually began. EDL have the same text by first hand. It
will be noted that the lines as given by TH₂ make no mention of the
schism of the Papacy.
11 ff. With this we may compare Mirour de l’Omme, 18769 ff.
22. nisi, for ‘nil nisi’: cp. l. 32.
41. Cp. Ovid, Amores, iii. 8. 55.
63. Fasti, i. 225.
65 f. Cp. Fasti, i. 249 f.
85-90. Chiefly from the Aurora of Petrus (de) Riga, (MS. Bodley
822) f. 71,
Gower is right in reading ‘serit,’ which is given in MS. Univ. Coll. 143,
f. 13.
1206. Cp. l. 1376.
1213. Cp. Ovid, Ars Amat. iii. 655.
1215 f. Cp. Ars Amat. iii. 653 f.
1233. Cp. Ars Amat. ii. 279.
1247 ff. Cp. Mirour, 18793 ff.
1267. Vox populi, &c.: cp. Speculum Stultorum, p. 100, l. 4, and
see also the note on iii. Prol. 11.
1271. Cp. Conf. Amantis, Prol. 304 ff. and Mirour, 18805.
1313. With the remainder of this Book, treating of the secular
clergy, we may compare Mirour de l’Omme, 20209-20832.
1341. Cp. Mirour, 18889 ff.
1342. participaret, ‘he ought to share’: see note on l. 676.
1359 f. Cp. Conf. Amantis, i. 1258 ff.
1375 ff. Cp. Mirour, 20287 ff.
1376. The reading ‘vngat vt’ is given by the Digby MS. and
seems almost necessary: cp. l. 1206.
1405. prece ruffi ... et albi, ‘by reason of the petition of the red
and the white,’ that is, presumably, by the influence of gold and
silver, ‘dominis’ in the next line being in a loose kind of apposition to
a dative case suggested by ‘Annuit.’
1407. S has here in the margin, in a rather later hand, ‘contra
rectores Oxon.’
1417. Eccles. iv. 10, ‘Vae soli, quia cum ceciderit, non habet
sublevantem se.’
1432. The margin of S has here, in the same hand as at 1407,
‘Nota rectores et studentes Oxon.’
1443. formalis, that is, ‘eminent,’ from ‘forma’ meaning ‘rank’ or
‘dignity,’ but here also opposed to ‘materialis.’
1454. Originally the line was ‘Dum legit, inde magis fit sibi sensus
hebes,’ but this was altered to ‘plus sibi sensus hebes est,’ with the
idea apparently of taking ‘magis’ with ‘legit.’ This involves an
awkward metrical licence, ‘hebes est’ equivalent to ‘hebest,’ and the
original text stands in CEH as well as in TH₂. The expedient of the
Roxburghe editor is quite inexcusable.
1493 ff. Cp. Mirour, 20314. The sporting parson was quite a
recognized figure in the fourteenth century. Readers of Froissart will
remember how when the capture of Terry in Albigeois was effected
by stratagem, the blowing of the horn to summon the company in
ambush was attributed by those at the gate to a priest going out into
the fields, ‘Ah that is true, it was sir Francis our priest; gladly he
goeth a mornings to seek for an hare.’
1498. fugat: used apparently as subjunctive also in l. 2078, but it
is possible that ‘Nec fugat’ may be the true reading here.
1509 ff. Cp. Mirour de l’Omme, 20313 ff.
1527. Est sibi missa, ‘his mass is over.’
1546. Apparently a proverbial expression used of wasting
valuable things.
1549. If benefices went from father to son, little or nothing would
be gained by those who go to Rome to seek preferment, for an heir
would seldom fail.
1555 ff. Cp. Mirour de l’Omme, 20497 ff. The priests here spoken
of are the ‘annuelers,’ who get their living by singing masses for the
dead, the ‘Annua seruicia’ spoken of below:
1807 f.
‘Aurum ueste gerit presul, cum splendet in illo
Pre cunctis rutilans clara sophia patris.’
Aurora, f. 45.
1809 ff.
1885 ff. Our author still borrows from the same source, though
from a different part of it. We find these lines nearly in the same form
in the Aurora, f. 103,
1891 f.
The meaning is that the bad priests cry ‘Cras,’ like crows, and
encourage men to put off repentance, while the others sing ‘Hodie,’
like doves, the words ‘cras’ and ‘hodie’ being imitations of the notes
of the two birds. The expression ‘Cras primam cantant,’ in l. 2039, is
not intelligible, and probably Gower missed the full sense of the
passage.
2045. ‘sit’ has been altered in S from ‘fit.’
2049 ff. Cp. Mirour de l’Omme, 20785 ff.
2071. Cp. Mirour, 20798.
2078. fugat: cp. l. 1498.
2097 f. Cp. iv. 959 and note.
LIB. IV.
The matter of this book corresponds to that of the Mirour de
l’Omme, ll. 20833-21780.
19 f. Cp. Lib. iii. Prol. 11.
34. ‘dompnus’ or ‘domnus’ was the form of ‘dominus’ which was
properly applied as a title to ecclesiastical dignitaries, and it seems
to have been especially used in monasteries. Ducange quotes John
of Genoa as follows: ‘Domnus et Domna per syncopen proprie
convenit claustralibus; sed Dominus, Domina mundanis.’ Cp. l. 323
of this book and also 327 ff.
57. humeris qui ferre solebat, ‘who used to bear burdens,’ as a
labourer.
87. Cp. Godfrey of Viterbo, Pantheon, p. 74 (ed. 1584).
91. Pantheon, p. 74.
109 f. Cp. Ovid, Fasti, i. 205 f.
111. Ars Amat. ii. 475, but Ovid has ‘cubilia.’
112. Cp. Fasti, iv. 396, ‘Quas tellus nullo sollicitante dabat.’
Gower has not improved the line by his changes.
114. Fasti, iv. 400.
115. Metam. i. 104, but Ovid has of course ‘fraga.’