Language dependent testing features #3819
Replies: 6 comments 41 replies
-
A simple thing that would satisfy both is to add a // In pair.gleam
// we'd have to first allow importing from the test/ dir, if not just allowed to be called in the @test annotation
import pair_test
@test(with: pair_test.get_first_string_test)
fn first(pair: #(a, b)) -> a {
pair.0
} // In pair_test.gleam
pub fn get_first_string_test() {
first(#("Hi", "mom"))
|> should.equal("Hi")
} Because cyclical imports are not allowed, this would enforce not importing the modules (and their functions) to be tested into the test module, but instead lead authors to add the In tests that use multiple functions: // In pair.gleam
import pair_test
@test(with: pair_test.map_second_test)
@test(with: pair_test.get_second_test)
fn second(pair: #(a, b)) -> b {
pair.0
}
@test(with: pair_test.map_second_test)
fn map_second(pair: #(a, b), to c) -> #(a, c) {
#(pair.0, to)
} // In pair_test.gleam
pub fn map_second_test() {
#("Hi", "Lucy")
|> map_second("friend")
|> second
|> should.equal("friend")
} This Gleam code would compile to Erlang or JS code that has the module imports reversed (so the test module imports the main one and not vice versa like in the Gleam code), and all the functions with the This // In pair.gleam
@test(input: #("Hi", "Lucy"), output: "Hi")
fn first(pair: #(a, b)) -> a {
pair.0
} A real world example of library code using this might look like this (using the actual tests and functions in the tempo package): import datetime_test as test
/// Parses a datetime string in the format `YYYY-MM-DDThh:mm:ss.sTZD`,
/// `YYYYMMDDThhmmss.sTZD`, `YYYY-MM-DD hh:mm:ss.sTZD`,
/// `YYYYMMDD hhmmss.sTZD`, `YYYY-MM-DD`, `YYYY-M-D`, `YYYY/MM/DD`,
/// `YYYY/M/D`, `YYYY.MM.DD`, `YYYY.M.D`, `YYYY_MM_DD`, `YYYY_M_D`,
/// `YYYY MM DD`, `YYYY M D`, or `YYYYMMDD`.
///
/// ## Examples
///
/// ```gleam
/// datetime.from_string("20240613T230400.009+00:00")
/// // -> datetime.literal("2024-06-13T23:04:00.009Z")
/// ```
@test(with: test.negative_offset_from_string_test)
@test(with: test.positive_offset_from_string_test)
@test(with: test.z_offset_from_string_test)
@test(with: test.condensed_negative_offset_from_string_test)
@test(with: test.condensed_offset_from_string_test)
@test(with: test.char_offset_from_string_test)
@test(with: test.space_delim_from_string_test)
@test(with: test.naive_from_string_test)
@test(with: test.date_out_of_bounds_from_string_test)
@test(with: test.time_out_of_bounds_from_string_test)
pub fn from_string(
datetime: String,
) -> Result(tempo.DateTime, tempo.DateTimeParseError) {
let split_dt = case string.contains(datetime, "T") {
True -> string.split(datetime, "T")
False -> string.split(datetime, " ")
}
case split_dt {
[date, time] -> {
use date: tempo.Date <- result.try(
date.from_string(date)
|> result.map_error(fn(e) { tempo.DateTimeDateParseError(e) }),
)
use #(time, offset): #(String, String) <- result.try(
split_time_and_offset(time),
)
use time: tempo.Time <- result.try(
time.from_string(time)
|> result.map_error(fn(e) { tempo.DateTimeTimeParseError(e) }),
)
use offset: tempo.Offset <- result.map(
offset.from_string(offset)
|> result.map_error(fn(e) { tempo.DateTimeOffsetParseError(e) }),
)
new(date, time, offset)
}
[date] ->
date.from_string(date)
|> result.map(new(_, tempo.time(0, 0, 0, 0, None, None), tempo.utc))
|> result.map_error(fn(e) { tempo.DateTimeDateParseError(e) })
_ -> Error(tempo.DateTimeInvalidFormat)
}
} Another cool thing this could enable is an in-code reminder to add tests later (maybe you thought of a niche test while in the middle of an implementation and want to write it down before you forget), emitting a warning but not actually crashing: @test(with: pair_test.map_second_test)
@test(with: todo as "get second with custom type test")
fn second(pair: #(a, b)) -> b {
pair.0
} |
Beta Was this translation helpful? Give feedback.
-
A solution that would generally solve item 2 would be to add some way to have friend modules, and then make the test module a friend to the main module so it can import its private values. |
Beta Was this translation helpful? Give feedback.
-
I don't love the idea of exposing private functions to a test module, or testing the private bits of a module in general. I'm sure there's plenty of exceptions to this and there could be a use for test functions inside a module to test its private bits but I haven't really felt that need so far :) |
Beta Was this translation helpful? Give feedback.
-
What happens if you forget to add a What do you do for tests that are not testing any specific function? There's no clear place to add the annotation such that the test function is turned into a test. |
Beta Was this translation helpful? Give feedback.
-
It is sometimes useful to have setup and teardown around all the tests, or around groups of tests. Did you have any thoughts for how this might work? |
Beta Was this translation helpful? Give feedback.
-
I'd love to have doctests:
And I'd like to have some coverage info from gleam tooling if a larger testing feature is added from to gleam tooling |
Beta Was this translation helpful? Give feedback.
-
Hey all, I wanted to start a discussion about two relatively simple things I'd love to see in the way we test Gleam code that would require new language features (and could not be implemented by a cool new testing framework):
There are lots of ways to implement these two things and there are pros and cons of each, maybe we can all discuss this in the comments :)
Beta Was this translation helpful? Give feedback.
All reactions