digitalmars.D.learn - Approach to Integration Testing in D
- Vijay Nayar (29/29) Feb 04 2022 Greetings everyone,
- Steven Schveighoffer (13/27) Feb 04 2022 I recently moved mysql-native integration tests to a subproject. I don't...
- Mathias LANG (8/14) Feb 04 2022 For server nodes, I've built a library just for this:
- H. S. Teoh (30/39) Feb 04 2022 Unittests are, by definition, *unit* tests, :-) meaning they are more
- Vijay Nayar (36/38) Feb 06 2022 I am still in the process of experimenting, but the advice on
- Vijay Nayar (63/65) Feb 06 2022 When working on a dub configuration needed to separately run
Greetings everyone, What is your approach to integration testing in D? Do you use `unittest` blocks? Do you write stand-alone programs that interact with a running version of your program? Is there a library that makes certain kinds of testing easier? For example, if I have a D project based on vibe.d, and I have custom converters to receive REST API request bodies in different formats based on the "Content-Type" HTTP header and other converter for the response based on the "Accepts" header, what is the best approach to test the entire program end-to-end? Having done considerable work in Java using Spring and Spring Boot, it's very common to have [integration tests](https://softwaretestingfundamentals.com/integration-testing/), which the Spring Boot framework makes quite easy to set up with annotations like [ SpringBootTest](https://www.baeldung.com/spring-boot-testing#integration-testing-with-springboottest). In a nutshell, Spring Boot is built on top of dependency injection, with a "Context" acting as a container for all the objects (called Beans) that are created and injected into that container. ` SpringBootTest` lets the user pick and choose which objects will be created for the test, and then when the program is run, only those objects are loaded. This allows one to do things like, start a complete web server environment, test by sending a request to some endpoint, and then validate that the response is what you would expect. This is especially helpful to validate that you are using the Spring Framework correctly, e.g. that custom converters you created that allow messages to be received in binary [protobuf](https://developers.google.com/protocol-buffers/) format instead of JSON work correctly.
Feb 04 2022
On 2/4/22 7:38 AM, Vijay Nayar wrote:Greetings everyone, What is your approach to integration testing in D? Do you use `unittest` blocks? Do you write stand-alone programs that interact with a running version of your program? Is there a library that makes certain kinds of testing easier? For example, if I have a D project based on vibe.d, and I have custom converters to receive REST API request bodies in different formats based on the "Content-Type" HTTP header and other converter for the response based on the "Accepts" header, what is the best approach to test the entire program end-to-end?I recently moved mysql-native integration tests to a subproject. I don't think unittesting should do integration tests *in the library itself*. But doing them via a unittesting framework in an external program seems to work well. Links: - common integration test project: https://github.com/mysql-d/mysql-native/tree/master/integration-tests - vibe tests: https://github.com/mysql-d/mysql-native/tree/master/integration-tests-vibe - phobos tests: https://github.com/mysql-d/mysql-native/tree/master/integration-tests-phobos -Steve
Feb 04 2022
On Friday, 4 February 2022 at 12:38:08 UTC, Vijay Nayar wrote:Greetings everyone, What is your approach to integration testing in D? Do you use `unittest` blocks? Do you write stand-alone programs that interact with a running version of your program? Is there a library that makes certain kinds of testing easier?For server nodes, I've built a library just for this: https://github.com/Geod24/localrest It required careful planning on the application side (e.g. we're not calling `runTask`, we have some classes for dependency injection), but allowed us to write "integration tests" in unittests block: https://github.com/bosagora/agora/tree/75967aac4f41272c6e5e8487c4066174825290e0/source/agora/test#agora-test-folder
Feb 04 2022
On Fri, Feb 04, 2022 at 12:38:08PM +0000, Vijay Nayar via Digitalmars-d-learn wrote: [...]What is your approach to integration testing in D? Do you use `unittest` blocks? Do you write stand-alone programs that interact with a running version of your program? Is there a library that makes certain kinds of testing easier?Unittests are, by definition, *unit* tests, :-) meaning they are more suitable for testing individual functions or modules, not really for integration testing of the entire program. Though in practice, the line is somewhat blurry, and I have written unittests that do test functionality across modules at times. The key is to write your code in a way that's amenable to testing, one principle of which is to avoid dependency on global state (cf. dependency injection). For example, a function that uses std.stdio.File could be made unittest-able by parametrizing `File` as a template parameter, so that a unittest block can inject a proxy type that performs the test without actually touching the filesystem (which could cause unwanted side-effects, esp. if the same file(s) are touched by multiple tests).For example, if I have a D project based on vibe.d, and I have custom converters to receive REST API request bodies in different formats based on the "Content-Type" HTTP header and other converter for the response based on the "Accepts" header, what is the best approach to test the entire program end-to-end?Depending on how you structured your program, templatizing types used by your program could make it unittestable on a large scale, e.g., if there was a way to inject your own request/response types into the code. Then unittests could just pass in mock request/response types containing test data and test program logic that way. In some cases, however, it may be difficult to do this on a program-wide scale, so in many cases an external program tester may be necessary. In some of my projects I've written helper programs that read a directory of test cases (which specifies program options, inputs, expected outputs, etc.) and invokes the program and compares its output to the expected output. This is hooked up to my build script so that it gets run automatically upon each rebuild. Then adding a test case is just a matter of adding some files to the directory and re-running the build. T -- They say that "guns don't kill people, people kill people." Well I think the gun helps. If you just stood there and yelled BANG, I don't think you'd kill too many people. -- Eddie Izzard, Dressed to Kill
Feb 04 2022
On Friday, 4 February 2022 at 17:39:00 UTC, H. S. Teoh wrote:On Fri, Feb 04, 2022 at 12:38:08PM +0000, Vijay Nayar via Digitalmars-d-learn wrote: [...]I am still in the process of experimenting, but the advice on this thread has all been very helpful. Right now I'm experimenting by creating a separate "integration" `dub` configuration which uses `std.process : spawnProcess` to run my vibe.d-based web server and try out requests on it. However, in any kind of large organization using a build server, even when there are multiple services/builds running in the same environment, it can be very inconvenient use a fixed port on which to run this server. Another running service, another person's build, or a test that ran and died without closing its sockets can lead to conflicts during development. In order to solve that problem of port conflicts during integration testing, I converted a utility class from the Java Spring Framework into D and wanted to share it here: https://gist.github.com/vnayar/04c6172d9f9991062974585bb3ccc8a4 The usage is very simple, and can be used by integration tests to pick random free ports on which to run their tests. Here is one of the available methods: ```d /** * Find an available TCP port randomly selected from the range * \[ [PORT_RANGE_MIN], [PORT_RANGE_MAX] \]. * Returns: an available TCP port number * Throws: Exception if no available port could be found */ ushort findAvailableTcpPort() { return findAvailableTcpPort(PORT_RANGE_MIN); } unittest { foreach (ushort i; 0..10) { ushort port = findAvailableTcpPort(); assert(port >= PORT_RANGE_MIN && port <= PORT_RANGE_MAX); } } ```
Feb 06 2022
On Sunday, 6 February 2022 at 17:36:05 UTC, Vijay Nayar wrote:On Friday, 4 February 2022 at 17:39:00 UTC, H. S. Teoh wrote:When working on a dub configuration needed to separately run integration tests that operate on the fully built program from the outside, treating it like a black-box, I had run into a number of problems because `dub` implicitly created configurations called `application` or `library` only if you have no configurations of your own defined. But as soon as I added my own configuration, those default configurations were no longer being created and I suddenly found myself seeing strange errors when trying to run unit-tests or builds. See [GitHub Dub Issue https://github.com/dlang/dub/issues/1270). However, I did finally navigate the tricky waters and make a configuration that does actually work for `dub build`, `dub test` and `dub test --config=integration`. I thought I would share what I found here: ```sdl authors "Vijay Nayar" copyright "Copyright © 2019, Vijay Nayar" description "Receives data from sources, converts it to protobufs, and delivers it in batches to stem." license "proprietary" name "mouth" targetPath "target" config if no forces us type, and configuration "application" { targetName "mouth" targetType "executable" dependency "funnel:common" path="../" dependency "funnel:proto" path="../" dependency "nanomsg-wrapper" version="~>0.5.3" dependency "poodinis" version="~>8.0.3" dependency "vibe-d" version="~>0.9.4" mainSourceFile "source/app.d" } configuration "integration" { targetName "mouth_integration" targetType "executable" explicitly excludedSourceFiles "source/*" sourcePaths "integration" importPaths "integration" preBuildCommands "echo IMPORT_PATHS=$$IMPORT_PATHS" preRunCommands "cd $PACKAGE_DIR ; dub build" } ```On Fri, Feb 04, 2022 at 12:38:08PM +0000, Vijay Nayar via
Feb 06 2022