Same for anonymous functions. You test the functions that use them and that's usually enough. If not, then that is a good indicator of separating them out into named functions.
> 20 short functions definitely sound as though they should be explicit.
And you are right that it's pretty arbitrary when/if a lambda should specifically be tested or not. But the number of lambdas in a project isn't really a good factor to make that decision - it's individual for each function that contains a lambda.
books
.join(authors, book => book.author, author => author.id)
.filter(([book, author]) => author.lastName === searchText)
.map((book, author) => `The Book ${book.title}, by ${author.fullName()}, has ${book.chapters.count()} chapters, totaling ${book.chapters.sum(chapter => chapter.pages.count())} pages.`);
There are 5 lambda functions in there. Can you tell what the code is doing? Is it correct? Yes, and yes. This is the kind of code that, in my opinion, doesn't need tests at all, nor comments. You can look at it and understand what it's doing and know that it is doing it correctly, as long as it compiles. If it's mission critical, you should test that the join really should be on book.author and author.id, but you need to know the correct answer to write the test, so why not just look at the code and verify it's correct? If your answer is "because another change might break it": no, it won't! Given the preconditions, that code is correct, and no other code can effectively break (and still have your code compile) it without lying to the Type-checker. If someone breaks that code, it's because they're intentionally changing it to do something else, so they'd redline any tests you wrote for it anyway.It sounds like you're suggesting this code should be more like:
/// <verboseDocs purpose="Verbosity">
/// <purpose> Gets the author of a book
/// <returns> The author of the given book
/// <inputs>
/// <input param="book">The book to get the author of
/// </inputs>
/// </verboseDocs>
function getAuthorOfBook(book: Book) {
return book.author;
}
@testMethod()
function canGetAuthorOfBook() {
const mockBook: Book = {
author: 'Test Author',
title: 'Verbosity',
...
};
Assert.areEqual('Test Author', getAuthorOfBook(mockBook);
}
... /// <verboseDocs>
/// <purpose> Takes a last name and returns a function that returns true if the given (Book, Author) tuple contains an author whose last name matches the given string of the outer function.
/// ...
function getBookAuthorPairPredicateFromAuthorLastName(lastName: string) {
return function(pair: [Book, Author])
return pair[1].lastName === lastName;
}
}
And on and on and on, still needing the original code, but just a lot more obfuscated: books
.join(authors, getAuthorOfBook, author => getIdOfAuthor)
.filter(getBookAuthorPairPredicateFromAuthorLastName (searchText))
.map(describeBookAuthorPair)
Now I have no idea what the hell this code is doing and whether it's correct or not. Note that to avoid lambdas in the filter (filtering by a value in the closure) we need to write a function that returns the predicate we want to test on, instead of just sticking the right thing in the right place to start with. All to avoid a single "=>".What circle of hell are we in!?
The context I was talking in mentioned Python not having lambdas. It does have lamdbas (there's even a keyword) so I assumed it meant larger, multi-line lambdas. The code you wrote (glueing together a few mini-lambdas) I would not expect to test independently. I'd feel bad that you can't use SQLAlchemy's even nicer syntax, but I certainly wouldn't expect a test for each of those little things.
My solution: array of lambdas
Your solution: array of references to functions named f1 .. f20.
Which is cleaner?
That's why you write functions like this:
def testXIsYAfterSettingAToBAndCToD():
A = B;
C = D;
assert(X == Y);
So much meaning provided by the name ;)* if anyone not get it, ts/js can declare a key-value lambda like:
let myFunc = {f1: () => , f2: () => } and so on. It can be called by string key like myFunc["f1"](), and all it's props can be get by Object.keys(myFunc) so it can be randomized easily.