You could use LaTeX's \@starttoc-\addtocontents-mechanism for this.
I tried to make sure the following example compiles both with and without hyperrref being loaded.
I also tried to make sure that \label-\ref-referencing and bookmarks works out.
You may need to compile the example several times without deleting auxiliary-files between LaTeX-runs for having everything match out correctly.
\documentclass{article}
\usepackage{hyperref}
\newcounter{TestMethod}
\newcounter{Requirement}
\makeatletter
\newcommand\listofrequirements{%
\section{Requirements}%
\IfFileExists{\jobname.lfr}{}{\par\noindent No requirements available.}%
\@starttoc{lfr}%
}%
\newcommand\listoftestmethods{%
\section{Testmethods}%
\begin{description}%
\def\makelabel##1{\hspace\labelsep\normalfont\bfseries##1}%
\IfFileExists{\jobname.ltm}{}{\item[\textnormal{No test-methods available.}]\relax}%
\@starttoc{ltm}%
\end{description}%
}%
\newcommand\@multipleRequirements{}%
\AtEndDocument{\@multipleRequirements}%
\begingroup
\catcode`\/=14 %
\catcode`\%=12 /
\@firstofone{/
\endgroup
\newcommand\Requirement[3]{/
\@bsphack
{/
\@ifundefined{Requirement@#1}{\global\@namedef{Requirement@#1}{DEFINED}}{/
\gdef\@multipleRequirements{\@latex@warning@no@line{There were multiply-defined requirements}}/
\@latex@warning{Requirement `#1' multiply defined}/
}/
}/
\begingroup
// Let's have \addtocontents / \protected@write write immediately:
\let\writecopy\write
\def\write{\immediate\writecopy}/
// Prevent all of \protected@write's expansion (\edef-with \protect-mechanism-expansion and \write-expansion)
// by nesting things either into two token-registers or into two \unexpanded.
\toks@{\the\@temptokena}/
\@temptokena\expandafter{\string\l@Requirement{%^^J{#1}%^^J{#2}%^^J}}/
\addtocontents{lfr}{\the\toks@\relax}/
\@temptokena\expandafter{\string\l@TestMethod{%^^J{#1}%^^J{#3}%^^J}}/
\addtocontents{ltm}{\the\toks@\relax}/
/\addtocontents{lfr}{\unexpanded{\unexpanded\expandafter{\string\l@Requirement{%^^J{#1}%^^J{#2}%^^J}}}\relax}/
/\addtocontents{ltm}{\unexpanded{\unexpanded\expandafter{\string\l@TestMethod{%^^J{#1}%^^J{#3}%^^J}}}\relax}/
\endgroup
\@esphack
}/
}%
\newcommand\l@Requirement[1]{%
%#1 = {ID}{<requirement-text>}
\PrintRequirement#1%
}%
\newcommand\l@TestMethod[1]{%
%#1 = {ID}{<requirement-text>}
\PrintTestMethod#1%
}%
\newcommand\PrintRequirement[2]{%
%#1 = ID; #2 = <requirement-text>;
\ifhmode\medskip\fi\par\noindent
\begingroup
\def\theRequirement{#1}%
\refstepcounter{Requirement}%
\def\@currentlabelname{#1}%
\label{RequirementNamespace@Requirement #1}%
\@ifundefined{Hy@raisedlink}{}{%
\@tempcnta\Hy@currentbookmarklevel
\Hy@StepCount\@tempcnta
\expandafter\RequirementPassFirstToSecond\expandafter{\the\@tempcnta}%
{\Hy@writebookmark{}{#1}{\@currentHref}}{toc}%
\advance \@tempcnta by -1 %
\xdef\Hy@currentbookmarklevel{\the \@tempcnta}%
\@ifundefined{r@RequirementNamespace@TestMethod #1}{}{%
\hyperref[{RequirementNamespace@TestMethod #1}]%
}%
}%
{\textbf{Requirement~ID~{#1}}}%
\\{#2}%
\endgroup
}%
\newcommand\Requirement@MoveLabelData{}%
\newcommand\RequirementPassFirstToSecond[2]{#2{#1}}%
\newcommand\PrintTestMethod[2]{%
%#1 = ID; #2 = <requirement-text>;
\item[{%
\def\theTestMethod{#1}%
\refstepcounter{TestMethod}%
\def\@currentlabelname{#1}%
\expandafter\gdef\expandafter\Requirement@MoveLabelData\expandafter{%
\romannumeral0%
\expandafter\RequirementPassFirstToSecond\expandafter{\@currentlabel}{%
\@ifundefined{Hy@raisedlink}{ }{%
\expandafter\RequirementPassFirstToSecond\expandafter{\@currentlabelname}{%
\expandafter\RequirementPassFirstToSecond\expandafter{\@currentHref}{ %<- This space must be!
\def\@currentHref
}%
\def\@currentlabelname
}%
}%
\def\@currentlabel
}%
}%
\label{RequirementNamespace@TestMethod #1}%
\@ifundefined{Hy@raisedlink}{}{%
\@tempcnta\Hy@currentbookmarklevel
\Hy@StepCount\@tempcnta
\expandafter\RequirementPassFirstToSecond\expandafter{\the\@tempcnta}%
{\Hy@writebookmark{}{#1}{\@currentHref}}{toc}%
\advance\@tempcnta by -1 %
\xdef\Hy@currentbookmarklevel{\the \@tempcnta}%
\@ifundefined{r@RequirementNamespace@Requirement #1}{}{%
\hyperref[{RequirementNamespace@Requirement #1}]%
}%
}%
{#1:}%
}]%
\Requirement@MoveLabelData
#2%
}%
\makeatother
\begin{document}
\section{This is just some section}
This is text in the section.
\noindent Reference to the requirement with ID ``SOMEOTHERID'': \ref{AA}.
\noindent Reference to the testing-method with ID ``SOMEOTHERID'': \ref{BB}.
\Requirement{SOMEID}{This is the requirement text.}{This describes the testmethod.}
\Requirement{SOMEOTHERID}{This \label{AA} is some different requirement text.}{This \label{BB} describes some different testmethod.}
\listofrequirements
\listoftestmethods
\end{document}
In case you get too long lines into the auxiliary-files, you can have some of the arguments read and written under verbatim-category-code-régime.
If you do that, the macro \Requirement cannot be used inside the definitions and/or arguments of other macros.
At first I could not get it to work with xparse's v+-argument-type, so I used my own thing.
I could not get it to work with xparse's v+-argument-type for the following reason: xparse's v+-argument-type uses the ^^M-character = the ⟨return⟩-character for denoting ends of lines. (^^M=^^-notation for the character whose code-point is 13 in the TeX-engine's internal character encoding scheme—M is the 13th letter in the alphapet; code-point 13 of the TeX-engine's internal character encoding scheme in turn denotes the ⟨return⟩-character.) So at the time of writing the v+-argument to file, the integer-parameter \newlinechar must also denote that character. Thus you'd better not use \protected@write for writing the v+-argument to file but something that does the \write immediately, while the change of \newlinechar is still in effect. In my first attempts with xparse's v+-argument-type I overlooked that the \write must take place immediately.
Here comes my own thing, but behind that I added another example, using xparse and its v+-argument-type for fetching the verbatimized arguments:
\documentclass{article}
\usepackage{hyperref}
\newcounter{TestMethod}
\newcounter{Requirement}
\makeatletter
%%======================Code for \UDcollectverbarg=============================
%% \UDcollectverbarg{^^M-replacement}{<mandatory 1>}{<mandatory 2>}|<verbatim arg>|
%%
%% reads <verbatim arg> under verbatim-catcode-regime and delivers:
%%
%% <mandatory 1>{<mandatory 2>{<verbatim arg>}{|<verbatim arg>|}}
%%
%% Instead of verbatim-delimiter | the <verbatim arg> can be nested in braces.
%% You cannot use percent or spaces or horizontal tab as verbatim-delimiter.
%%
%% You can use <mandatory 1> for nesting calls to \UDcollectverbarg.
%% <mandatory 2> gets the <verbatim arg> twice: Once without verbatim-delimiters/braces,
%% once surrounded by verbatim-delimiters/braces.
%% Reason: When you feed it to \scantokens you don't need the verbatim-delimiters.
%% When you use it for writing to temporary files and reading back,
%% you need them.
%%=============================================================================
%% Check whether argument is empty:
%%=============================================================================
%% \UD@CheckWhetherNull{<Argument which is to be checked>}%
%% {<Tokens to be delivered in case that argument
%% which is to be checked is empty>}%
%% {<Tokens to be delivered in case that argument
%% which is to be checked is not empty>}%
%%
%% Due to \romannumeral0-expansion the result is delivered after two
%% expansion-steps/after two "hits" by \expandafter.
%%
%% The gist of this macro comes from Robert R. Schneck's \ifempty-macro:
%% <https://groups.google.com/forum/#!original/comp.text.tex/kuOEIQIrElc/lUg37FmhA74J>
%%
\long\def\UD@CheckWhetherNull#1{%
\romannumeral0\expandafter\@secondoftwo\string{\expandafter
\@secondoftwo\expandafter{\expandafter{\string#1}\expandafter
\@secondoftwo\string}\expandafter\@firstoftwo\expandafter{\expandafter
\@secondoftwo\string}\@firstoftwo\expandafter{} \@secondoftwo}%
{\@firstoftwo\expandafter{} \@firstoftwo}%
}%
%%=============================================================================
\begingroup
\@makeother\^^M%
\@firstofone{%
\endgroup%
\newcommand\UDEndlreplace[2]{\romannumeral0\@UDEndlreplace{#2}#1^^M\relax{}}%
\@ifdefinable\@UDEndlreplace{%
\long\def\@UDEndlreplace#1#2^^M#3\relax#4#5{%
\UD@CheckWhetherNull{#3}%
{ #5{#4#2}}{\@UDEndlreplace{#1}#3\relax{#4#2#1}{#5}}%
}%
}%
}%
\newcommand\UDcollectverbarg[3]{%
\begingroup
\let\do\@makeother % <- this and the next line switch to
\dospecials % verbatim-category-code-régime.
\catcode`\{=1 % <- give opening curly brace the usual catcode so a
% curly-brace-balanced argument can be gathered in
% case of the first thing of the verbatimized-argument
% being a curly opening brace.
\catcode`\ =10 % <- give space and horizontal tab the usual catcode so \UD@collectverbarg
\catcode`\^^I=10 % cannot catch a space or a horizontal tab as its 4th undelimited argument.
% (Its 4th undelimited argument denotes the verbatim-
% syntax-delimiter in case of not gathering a
% curly-brace-nested argument.)
\catcode`\%=14 % <- make percent comment.
\kernel@ifnextchar\bgroup
{% seems a curly-brace-nested argument is to be caught:
\catcode`\}=2 % <- give closing curly brace the usual catcode also.
\UD@collectverbarg{#1}{#2}{#3}{}%
}{% seems an argument with verbatim-syntax-delimiter is to be caught:
\do\{% <- give opening curly brace the verbatim-catcode again.
\UD@collectverbarg{#1}{#2}{#3}%
}%
}%
\newcommand\UD@collectverbarg[4]{%
\do\ % <- Now that \UD@collectverbarg has the delimiter or
\do\^^I% emptiness in its 4th arg, give space and horizontal tab
% the verbatim-catcode again.
\do\^^M% <- Give the carriage-return-character the verbatim-catcode.
\do\%% <- Give the percent-character the verbatim-catcode.
\long\def\@tempb##1#4{%
\def\@tempb{##1}%
\UD@CheckWhetherNull{#4}{%
\def\@tempc{{##1}}%
}{%
\def\@tempc{#4##1#4}%
}%
\@onelevel@sanitize\@tempb % <- Turn characters into their "12/other"-pendants.
% This may be important with things like the
% inputenc-package which may make characters
% active/which give them catcode 13(active).
\expandafter\UDEndlreplace\expandafter{\@tempb}{#1}{\def\@tempb}% <- this starts
% the loop for replacing endline-characters.
\@onelevel@sanitize\@tempc
\expandafter\UDEndlreplace\expandafter{\@tempc}{#1}{\def\@tempc}%
\expandafter\expandafter\expandafter\UD@@collectverbarg% <- this "spits out the result.
\expandafter\expandafter\expandafter{%
\expandafter\@tempb\expandafter}%
\expandafter{\@tempc}{#2}{#3}%
}%
\@tempb
}%
\newcommand\UD@@collectverbarg[4]{%
\endgroup
#3{#4{#1}{#2}}%
}%
%%================= End of code for \UDcollectverbarg =========================
\newcommand\listofrequirements{%
\section{Requirements}%
\IfFileExists{\jobname.lfr}{}{\par\noindent No requirements available.}%
\@starttoc{lfr}%
}%
\newcommand\listoftestmethods{%
\section{Testmethods}%
\begin{description}%
\def\makelabel##1{\hspace\labelsep\normalfont\bfseries##1}%
\IfFileExists{\jobname.ltm}{}{\item[\textnormal{No test-methods available.}]\relax}%
\@starttoc{ltm}%
\end{description}%
}%
\newcommand\@multipleRequirements{}%
\AtEndDocument{\@multipleRequirements}%
\newcommand\Requirement[1]{%
\@bsphack
\UDcollectverbarg{^^J}{%
\UDcollectverbarg{^^J}{\@firstofone}%
}{\InnerRequirement{#1}}%
}%
\newcommand\InnerRequirement[5]{%
{%
\@ifundefined{Requirement@#1}{\global\@namedef{Requirement@#1}{DEFINED}}{%
\gdef\@multipleRequirements{\@latex@warning@no@line{There were multiply-defined requirements}}%
\@latex@warning{Requirement `#1' multiply defined}%
}%
}%
\begingroup
\immediate\write\@auxout{\string\Requirementwritefile{lfr}{Requirement}{#1}#3}%
\immediate\write\@auxout{\string\Requirementwritefile{ltm}{TestMethod}{#1}#5}%
\endgroup
\@esphack
}%
\newcommand\Requirementwritefile[3]{%
\UDcollectverbarg{^^J}{\@firstofone}{%
\expandafter\expandafter\expandafter\RequirementPassFirstToSecond
\expandafter\expandafter\expandafter{\expandafter\string\csname l@#2\endcsname}%
{\RequirementwritefileInner{#1}}{#3}%
}%
}%
\begingroup
\catcode`\/=14 %
\@makeother{\%}/
\@firstofone{/
\endgroup
\newcommand\RequirementwritefileInner[5]{/
\@writefile{#1}{/
#2{%^^J/
{#3}%^^J/
{#4}%^^J/
}%/
}/
}/
}%
\newcommand\l@Requirement[1]{%
%#1 = {ID}{<requirement-text>}
\PrintRequirement#1%
}%
\newcommand\l@TestMethod[1]{%
%#1 = {ID}{<requirement-text>}
\PrintTestMethod#1%
}%
\newcommand\PrintRequirement[2]{%
%#1 = ID; #2 = <requirement-text>;
\ifhmode\medskip\fi\par\noindent
\begingroup
\def\theRequirement{#1}%
\refstepcounter{Requirement}%
\def\@currentlabelname{#1}%
\label{RequirementNamespace@Requirement #1}%
\@ifundefined{Hy@raisedlink}{}{%
\@tempcnta\Hy@currentbookmarklevel
\Hy@StepCount\@tempcnta
\expandafter\RequirementPassFirstToSecond\expandafter{\the\@tempcnta}%
{\Hy@writebookmark{}{#1}{\@currentHref}}{toc}%
\advance\@tempcnta by -1 %
\xdef\Hy@currentbookmarklevel{\the\@tempcnta}%
\@ifundefined{r@RequirementNamespace@TestMethod #1}{}{%
\hyperref[{RequirementNamespace@TestMethod #1}]%
}%
}%
{\textbf{Requirement~ID~{#1}}}%
\\{#2}%
\endgroup
}%
\newcommand\Requirement@MoveLabelData{}%
\newcommand\RequirementPassFirstToSecond[2]{#2{#1}}%
\newcommand\PrintTestMethod[2]{%
%#1 = ID; #2 = <requirement-text>;
\item[{%
\def\theTestMethod{#1}%
\refstepcounter{TestMethod}%
\def\@currentlabelname{#1}%
\expandafter\gdef\expandafter\Requirement@MoveLabelData\expandafter{%
\romannumeral0%
\expandafter\RequirementPassFirstToSecond\expandafter{\@currentlabel}{%
\@ifundefined{Hy@raisedlink}{ }{%
\expandafter\RequirementPassFirstToSecond\expandafter{\@currentlabelname}{%
\expandafter\RequirementPassFirstToSecond\expandafter{\@currentHref}{ %<- This space must be!
\def\@currentHref
}%
\def\@currentlabelname
}%
}%
\def\@currentlabel
}%
}%
\label{RequirementNamespace@TestMethod #1}%
\@ifundefined{Hy@raisedlink}{}{%
\@tempcnta\Hy@currentbookmarklevel
\Hy@StepCount\@tempcnta
\expandafter\RequirementPassFirstToSecond\expandafter{\the\@tempcnta}%
{\Hy@writebookmark{}{#1}{\@currentHref}}{toc}%
\advance\@tempcnta by -1 %
\xdef\Hy@currentbookmarklevel{\the\@tempcnta}%
\@ifundefined{r@RequirementNamespace@Requirement #1}{}{%
\hyperref[{RequirementNamespace@Requirement #1}]%
}%
}%
{#1:}%
}]%
\Requirement@MoveLabelData
#2%
}%
\makeatother
\begin{document}
\section{This is just some section}
This is text in the section.
\noindent Reference to the requirement with ID ``SOMEOTHERID'': \ref{AA}.
\noindent Reference to the testing-method with ID ``SOMEOTHERID'': \ref{BB}.
\Requirement{SOMEID}{This is the requirement text.}{This describes the testmethod.}
\Requirement{SOMEOTHERID}{This \label{AA} is some different requirement text.}{This \label{BB} describes some different testmethod.}
\listofrequirements
\listoftestmethods
\end{document}
Here is the example with the verbatimized argument, using xparse's +v-argument-type:
\documentclass{article}
\usepackage{xparse}
\usepackage{hyperref}
\newcounter{TestMethod}
\newcounter{Requirement}
\makeatletter
\NewDocumentCommand{\listofrequirements}{}{%
\section{Requirements}%
\IfFileExists{\jobname.lfr}{}{\par\noindent No requirements available.}%
\@starttoc{lfr}%
}%
\NewDocumentCommand{\listoftestmethods}{}{%
\section{Testmethods}%
\begin{description}%
\def\makelabel##1{\hspace\labelsep\normalfont\bfseries##1}%
\IfFileExists{\jobname.ltm}{}{\item[\textnormal{No test-methods available.}]\relax}%
\@starttoc{ltm}%
\end{description}%
}%
\NewDocumentCommand{\@multipleRequirements}{}{}%
\AtEndDocument{\@multipleRequirements}%
\NewDocumentCommand{\Requirement}{m}{%
\begingroup
\@makeother{\^^I}%
\InnerRequirement{#1}%
}%
\begingroup
\catcode`\/=14 %
\@makeother{\%}/
\@makeother{\^^M}/
\@firstofone{/
\endgroup/
\NewDocumentCommand{\InnerRequirement}{m+v+v}{/
\endgroup/
{/
\@ifundefined{Requirement@#1}{\global\@namedef{Requirement@#1}{DEFINED}}{/
\gdef\@multipleRequirements{\@latex@warning@no@line{There were multiply-defined requirements}}/
\@latex@warning{Requirement `#1' multiply defined}/
}/
}/
\begingroup/
\newlinechar=\endlinechar/
\immediate\write\@auxout{/
\string\Requirementwritefile{lfr}/
{\unexpanded\expandafter{\string\l@Requirement{%^^M{#1}%^^M{#2}%^^M}}%}/
}/
\immediate\write\@auxout{/
\string\Requirementwritefile{ltm}/
{\unexpanded\expandafter{\string\l@TestMethod{%^^M{#1}%^^M{#3}%^^M}}%}/
}/
\endgroup/
\@esphack/
}/
}%
\NewDocumentCommand{\Requirementwritefile}{m}{%
\begingroup
\@makeother{\^^I}%
\RequirementwritefileInner{#1}%
}%
\NewDocumentCommand{\RequirementwritefileInner}{m+v}{%
\newlinechar=\endlinechar
\@writefile{#1}{#2}%
\endgroup
}%
\NewDocumentCommand{\l@Requirement}{+m}{%
%#1 = {ID}{<requirement-text>}
\PrintRequirement#1%
}%
\NewDocumentCommand{\l@TestMethod}{+m}{%
%#1 = {ID}{<requirement-text>}
\PrintTestMethod#1%
}%
\NewDocumentCommand{\PrintRequirement}{+m+m}{%
%#1 = ID; #2 = <requirement-text>;
\ifhmode\medskip\fi\par\noindent
\begingroup
\def\theRequirement{#1}%
\refstepcounter{Requirement}%
\def\@currentlabelname{#1}%
\label{RequirementNamespace@Requirement #1}%
\@ifundefined{Hy@raisedlink}{}{%
\@tempcnta\Hy@currentbookmarklevel
\Hy@StepCount\@tempcnta
\expandafter\RequirementPassFirstToSecond\expandafter{\the\@tempcnta}%
{\Hy@writebookmark{}{#1}{\@currentHref}}{toc}%
\advance\@tempcnta by -1 %
\xdef\Hy@currentbookmarklevel{\the \@tempcnta}%
\@ifundefined{r@RequirementNamespace@TestMethod #1}{}{%
\hyperref[{RequirementNamespace@TestMethod #1}]%
}%
}%
{\textbf{Requirement~ID~{#1}}}%
\\{#2}%
\endgroup
}%
\NewDocumentCommand{\Requirement@MoveLabelData}{}{}%
\NewDocumentCommand{\RequirementPassFirstToSecond}{+m+m}{#2{#1}}%
\NewDocumentCommand{\PrintTestMethod}{+m+m}{%
%#1 = ID; #2 = <requirement-text>;
\item[{%
\def\theTestMethod{#1}%
\refstepcounter{TestMethod}%
\def\@currentlabelname{#1}%
\expandafter\gdef\expandafter\Requirement@MoveLabelData\expandafter{%
\romannumeral0%
\expandafter\RequirementPassFirstToSecond\expandafter{\@currentlabel}{%
\@ifundefined{Hy@raisedlink}{ }{%
\expandafter\RequirementPassFirstToSecond\expandafter{\@currentlabelname}{%
\expandafter\RequirementPassFirstToSecond\expandafter{\@currentHref}{ %<- This space must be!
\def\@currentHref
}%
\def\@currentlabelname
}%
}%
\def\@currentlabel
}%
}%
\label{RequirementNamespace@TestMethod #1}%
\@ifundefined{Hy@raisedlink}{}{%
\@tempcnta\Hy@currentbookmarklevel
\Hy@StepCount\@tempcnta
\expandafter\RequirementPassFirstToSecond\expandafter{\the\@tempcnta}%
{\Hy@writebookmark{}{#1}{\@currentHref}}{toc}%
\advance \@tempcnta by -1 %
\xdef\Hy@currentbookmarklevel{\the \@tempcnta}%
\@ifundefined{r@RequirementNamespace@Requirement #1}{}{%
\hyperref[{RequirementNamespace@Requirement #1}]%
}%
}%
{#1:}%
}]%
\Requirement@MoveLabelData
#2%
}%
\makeatother
\begin{document}
\section{This is just some section}
This is text in the section.
\noindent Reference to the requirement with ID ``SOMEOTHERID'': \ref{AA}.
\noindent Reference to the testing-method with ID ``SOMEOTHERID'': \ref{BB}.
\Requirement{SOMEID}{This is the requirement text.}{This describes the testmethod.}
\Requirement{SOMEOTHERID}{This \label{AA} is some different requirement text.}{This \label{BB} describes some different testmethod.}
\listofrequirements
\listoftestmethods
\end{document}
