오늘은 gtest에 대해서 이야기해볼 예정입니다. 제 캡스톤 주제는 fuzzing인데 fuzzing에 앞서 testing을 어떻게 하는 것이고, 특히 Unit test는 어떻게 하는 것이고 무엇인지에 대해서 이번에 배워봤습니다.
다들 아시다시피 gtest는 Unit test를 편리하게 하기 위해서 만들어진 Framework입니다.(제 담당 교수님은 gtest 소개해주기 전에 source code를 가지고 unit test를 해보라고 하셔서 source code의 헤더파일을 따고, 따로 test code 만들어서 ifdef로 main문 중첩 없애주고 등등... 이러한 고생을 먼저하게 하셨답니다.. 고생해봐야 편리함을 알지..)
아무튼 gtest를 사용하여서 저희가 전에 만든 FTP 프로그램을 testing했습니다. 여기서 중요한 것은, gtest는 C++를 베이스로한 Framework입니다. 저희가 만든 것은 Pure C 프로그램이었습니다. 그래서 gtest에서 C 프로그램을 Unit testing하기 위해서는 C언어 기반의 함수들을 C style로 compile되게 만들어야합니다. 이 말은 즉슨, test code는 C++이고 해당 코드 내에서 C 프로그램 함수를 declaration해놓은 헤더파일을 extern "C"로 감싸야합니다. 아래와 같이 말이죠
이렇게 하면 c++ compiler가 해당 코드는 c style로 컴파일합니다. 참 쉽쬬잉?
이후에는 gtest에서 하는 것 처럼 TEST(TestSuiteName, TestName)문 안에 test하고자 하는 함수를 매크로와 함께 넣으면 되겠습니다. 여기서 매크로라고 하면 EXPECT_EQ(foo(), 1);에서 EXPECT_EQ같은 것을 의미합니다. 매크로의 종류는 굉장히 엄청나! 많습니다. 아래의 공식 사이트를 참고하셔요. https://google.github.io/googletest/reference/testing.html
그럼 대충 이러한 모양이 됩니다! 참 쉽쬬잉.
그런데 이 프로그램은 socket program이기에 send나 recv같은 함수가 있습니다. 그런데 이러한 함수를 사용하는 함수를 unit test하려면 socket이 존재해야하는데 Unit test이기에 그 환경을 맞추기는 어렵습니다. 그래서 사용해야할 것이 Gmock입니다. 공식문서에 Mock은 이러하다고 합니다.
Mocks are objects pre-programmed with expectations, which form a specification of the calls they are expected to receive.
에상한 값을 미리 programing해둔 것이라고 하죠. 후에 설명하겠지만 EXPECT_CALL같은 매크로로 Mocking function의 return value를 설정할 수 있습니다. 그러므로 Mocking은 어떤 함수를 바꿔쳐서 해당 함수를 내 마음대로 조작할 수 있게 하기에 매우 유용합니다.
그런데 여기서 진짜진짜 문제가 있습니다❗️
그냥 Mocking하는거면 괜찮은데 우리는 C 프로그램이라서 Mocking하는게 어렵습니다. 왜냐하면 C++에서는 해당 Mocking용 Class를 만들고 해당 Class내에서 mocking할 함수를 MOCK_METHOD에 적어주면 mocking이 되는데 C는 객체지향을 지원하지 않기 때문에 이게 overriding이 되지 않습니다. 그래서 단순히 위와 같은 방식을 가지고는 되지가 않습니다.
저희 팀이 찾아낸 방식이 몇가지 있는데, 첫 번째는 linking-time Interposition입니다. 컴파일러가 기본적으로 dynamic linking을 이용한다는 점을 이용해서 제가 작성한 함수를 linking level에서 먼저 붙여주는 것입니다. 그렇게 된다면 원래 run-time linking되어야할 함수들이 미리 붙어있는 것이기 때문에 run-time에서는 해당 함수를 찾지 않게 됩니다. 여기서의 문제점은 이렇게 했을 때 저희가 만든 함수대로 작동하긴 했지만, Mocking은 아니어서 GMock의 강력한 기능을 사용하지 못합니다. 두 번째는 run-time linking에서 LD_PRELOAD 환경변수에 우리가 만든 shared library를 붙여줘서 run-time linking시에 우리가 만든 함수를 먼저 찾는 것인데, 이는 위와 동일한 결과를 가지고 있습니다.
그래서 저희는 마지막으로 찾은 세 번째 방법은 사용했습니다.
먼저 해당 함수를 Mocking하기 위해서 먼저 Wrapper class와 virtual type을 이용해서 있는 것처럼의 가짜 함수를 만들어줍니다.
이렇게 한 후 해당 Class를 상속받은 Mocking Class를 만들어서 해당 method를 Mocking해줍니다. 이렇게 하면 가짜 함수가 mocking됩니다.
이후 중요한 것은 가짜 함수를 Mocking했는데, 이렇게 Mocking된 함수를 원래 함수보다 먼저 linking되도록 붙여주는 것입니다. 아래 사진처럼 test code내에서 이렇게 해주면 첫 번째 방법과 동일하게 linking-time에 제가 만들어준 함수를 먼저 찾게 되어서 이후에 나오는 것을 무시하게 됩니다.
마지막 방법이 첫 번째 말씀 드렸던 방법과 다른 이유는 Mocking한 함수로 대체했느냐, Mocking되지 않은 함수로 대체했느냐입니다. 첫 번째 함수는 제가 그냥 만든 함수로 대체한 것이고, 마지막 방법은 C++에서 가짜 함수를 만들어서 Mocking시키고 해당 함수를 가르키게 한 것이기 때문입니다,
혹시나 C 프로그램을 Unit test할 일이 있으시다면 위 방법 참고하셔서 gtest나 gmock을 사용하시길 바랍니다! 지적할 점이나 궁금하신거 있으시면 Feel free~
오늘도 감사합니다!